create(Translations translations, PlanetilerConfig config, Stats stats) {
15 | return List.of(
16 | // Create classes that extend Layer interface in the addons package, then instantiate them here
17 | );
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/main/java/org/openmaptiles/layers/AerodromeLabel.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) 2021, MapTiler.com & OpenMapTiles contributors.
3 | All rights reserved.
4 |
5 | Code license: BSD 3-Clause License
6 |
7 | Redistribution and use in source and binary forms, with or without
8 | modification, are permitted provided that the following conditions are met:
9 |
10 | * Redistributions of source code must retain the above copyright notice, this
11 | list of conditions and the following disclaimer.
12 |
13 | * Redistributions in binary form must reproduce the above copyright notice,
14 | this list of conditions and the following disclaimer in the documentation
15 | and/or other materials provided with the distribution.
16 |
17 | * Neither the name of the copyright holder nor the names of its
18 | contributors may be used to endorse or promote products derived from
19 | this software without specific prior written permission.
20 |
21 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
22 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
24 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
25 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
27 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
28 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
29 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 |
32 | Design license: CC-BY 4.0
33 |
34 | See https://github.com/openmaptiles/openmaptiles/blob/master/LICENSE.md for details on usage
35 | */
36 | package org.openmaptiles.layers;
37 |
38 | import static org.openmaptiles.util.Utils.nullIfEmpty;
39 | import static org.openmaptiles.util.Utils.nullOrEmpty;
40 |
41 | import com.onthegomap.planetiler.FeatureCollector;
42 | import com.onthegomap.planetiler.config.PlanetilerConfig;
43 | import com.onthegomap.planetiler.expression.MultiExpression;
44 | import com.onthegomap.planetiler.stats.Stats;
45 | import com.onthegomap.planetiler.util.Translations;
46 | import org.openmaptiles.generated.OpenMapTilesSchema;
47 | import org.openmaptiles.generated.Tables;
48 | import org.openmaptiles.util.OmtLanguageUtils;
49 | import org.openmaptiles.util.Utils;
50 |
51 | /**
52 | * Defines the logic for generating map elements in the {@code aerodrome_label} layer from source features.
53 | *
54 | * This class is ported to Java from
55 | * OpenMapTiles
56 | * aerodrome_layer sql files.
57 | */
58 | public class AerodromeLabel implements
59 | OpenMapTilesSchema.AerodromeLabel,
60 | Tables.OsmAerodromeLabelPoint.Handler {
61 |
62 | private final MultiExpression.Index classLookup;
63 | private final Translations translations;
64 |
65 | public AerodromeLabel(Translations translations, PlanetilerConfig config, Stats stats) {
66 | this.classLookup = FieldMappings.Class.index();
67 | this.translations = translations;
68 | }
69 |
70 | @Override
71 | public void process(Tables.OsmAerodromeLabelPoint element, FeatureCollector features) {
72 | String clazz = classLookup.getOrElse(element.source(), FieldValues.CLASS_OTHER);
73 | boolean important = !nullOrEmpty(element.iata()) && FieldValues.CLASS_INTERNATIONAL.equals(clazz);
74 | features.centroid(LAYER_NAME)
75 | .setBufferPixels(BUFFER_SIZE)
76 | .setMinZoom(important ? 8 : 10)
77 | .putAttrs(OmtLanguageUtils.getNames(element.source().tags(), translations))
78 | .putAttrs(Utils.elevationTags(element.ele()))
79 | .setAttr(Fields.IATA, nullIfEmpty(element.iata()))
80 | .setAttr(Fields.ICAO, nullIfEmpty(element.icao()))
81 | .setAttr(Fields.CLASS, clazz);
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/src/main/java/org/openmaptiles/layers/Aeroway.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) 2021, MapTiler.com & OpenMapTiles contributors.
3 | All rights reserved.
4 |
5 | Code license: BSD 3-Clause License
6 |
7 | Redistribution and use in source and binary forms, with or without
8 | modification, are permitted provided that the following conditions are met:
9 |
10 | * Redistributions of source code must retain the above copyright notice, this
11 | list of conditions and the following disclaimer.
12 |
13 | * Redistributions in binary form must reproduce the above copyright notice,
14 | this list of conditions and the following disclaimer in the documentation
15 | and/or other materials provided with the distribution.
16 |
17 | * Neither the name of the copyright holder nor the names of its
18 | contributors may be used to endorse or promote products derived from
19 | this software without specific prior written permission.
20 |
21 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
22 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
24 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
25 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
27 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
28 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
29 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 |
32 | Design license: CC-BY 4.0
33 |
34 | See https://github.com/openmaptiles/openmaptiles/blob/master/LICENSE.md for details on usage
35 | */
36 | package org.openmaptiles.layers;
37 |
38 | import com.onthegomap.planetiler.FeatureCollector;
39 | import com.onthegomap.planetiler.config.PlanetilerConfig;
40 | import com.onthegomap.planetiler.stats.Stats;
41 | import com.onthegomap.planetiler.util.Translations;
42 | import org.openmaptiles.generated.OpenMapTilesSchema;
43 | import org.openmaptiles.generated.Tables;
44 |
45 | /**
46 | * Defines the logic for generating map elements in the {@code aeroway} layer from source features.
47 | *
48 | * This class is ported to Java from
49 | * OpenMapTiles aeroway sql files.
50 | */
51 | public class Aeroway implements
52 | OpenMapTilesSchema.Aeroway,
53 | Tables.OsmAerowayLinestring.Handler,
54 | Tables.OsmAerowayPolygon.Handler,
55 | Tables.OsmAerowayPoint.Handler {
56 |
57 | public Aeroway(Translations translations, PlanetilerConfig config, Stats stats) {}
58 |
59 | @Override
60 | public void process(Tables.OsmAerowayPolygon element, FeatureCollector features) {
61 | features.polygon(LAYER_NAME)
62 | .setMinZoom(10)
63 | .setMinPixelSize(2)
64 | .setAttr(Fields.CLASS, element.aeroway())
65 | .setAttr(Fields.REF, element.ref());
66 | }
67 |
68 | @Override
69 | public void process(Tables.OsmAerowayLinestring element, FeatureCollector features) {
70 | features.line(LAYER_NAME)
71 | .setMinZoom(10)
72 | .setAttr(Fields.CLASS, element.aeroway())
73 | .setAttr(Fields.REF, element.ref());
74 | }
75 |
76 | @Override
77 | public void process(Tables.OsmAerowayPoint element, FeatureCollector features) {
78 | features.point(LAYER_NAME)
79 | .setMinZoom(14)
80 | .setAttr(Fields.CLASS, element.aeroway())
81 | .setAttr(Fields.REF, element.ref());
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/src/main/java/org/openmaptiles/layers/Building.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) 2023, MapTiler.com & OpenMapTiles contributors.
3 | All rights reserved.
4 |
5 | Code license: BSD 3-Clause License
6 |
7 | Redistribution and use in source and binary forms, with or without
8 | modification, are permitted provided that the following conditions are met:
9 |
10 | * Redistributions of source code must retain the above copyright notice, this
11 | list of conditions and the following disclaimer.
12 |
13 | * Redistributions in binary form must reproduce the above copyright notice,
14 | this list of conditions and the following disclaimer in the documentation
15 | and/or other materials provided with the distribution.
16 |
17 | * Neither the name of the copyright holder nor the names of its
18 | contributors may be used to endorse or promote products derived from
19 | this software without specific prior written permission.
20 |
21 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
22 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
24 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
25 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
27 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
28 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
29 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 |
32 | Design license: CC-BY 4.0
33 |
34 | See https://github.com/openmaptiles/openmaptiles/blob/master/LICENSE.md for details on usage
35 | */
36 | package org.openmaptiles.layers;
37 |
38 | import static com.onthegomap.planetiler.util.MemoryEstimator.CLASS_HEADER_BYTES;
39 | import static com.onthegomap.planetiler.util.Parse.parseDoubleOrNull;
40 | import static java.util.Map.entry;
41 | import static org.openmaptiles.util.Utils.coalesce;
42 |
43 | import com.onthegomap.planetiler.FeatureCollector;
44 | import com.onthegomap.planetiler.FeatureMerge;
45 | import com.onthegomap.planetiler.ForwardingProfile;
46 | import com.onthegomap.planetiler.VectorTile;
47 | import com.onthegomap.planetiler.config.PlanetilerConfig;
48 | import com.onthegomap.planetiler.geo.GeometryException;
49 | import com.onthegomap.planetiler.reader.osm.OsmElement;
50 | import com.onthegomap.planetiler.reader.osm.OsmRelationInfo;
51 | import com.onthegomap.planetiler.stats.Stats;
52 | import com.onthegomap.planetiler.util.MemoryEstimator;
53 | import com.onthegomap.planetiler.util.Translations;
54 | import java.util.List;
55 | import java.util.Locale;
56 | import java.util.Map;
57 | import org.openmaptiles.generated.OpenMapTilesSchema;
58 | import org.openmaptiles.generated.Tables;
59 |
60 | /**
61 | * Defines the logic for generating map elements for buildings in the {@code building} layer from source features.
62 | *
63 | * This class is ported to Java from
64 | * OpenMapTiles building sql
65 | * files.
66 | */
67 | public class Building implements
68 | OpenMapTilesSchema.Building,
69 | Tables.OsmBuildingPolygon.Handler,
70 | ForwardingProfile.LayerPostProcessor,
71 | ForwardingProfile.OsmRelationPreprocessor {
72 |
73 | /*
74 | * Emit all buildings from OSM data at z14.
75 | *
76 | * At z13, emit all buildings at process-time, but then at tile render-time,
77 | * merge buildings that are overlapping or almost touching into combined
78 | * buildings so that entire city blocks show up as a single building polygon.
79 | *
80 | * THIS IS VERY EXPENSIVE! Merging buildings at z13 adds about 50% to the
81 | * total map generation time. To disable it, set building_merge_z13 argument
82 | * to false.
83 | */
84 |
85 | private static final Map MATERIAL_COLORS = Map.ofEntries(
86 | entry("cement_block", "#6a7880"),
87 | entry("brick", "#bd8161"),
88 | entry("plaster", "#dadbdb"),
89 | entry("wood", "#d48741"),
90 | entry("concrete", "#d3c2b0"),
91 | entry("metal", "#b7b1a6"),
92 | entry("stone", "#b4a995"),
93 | entry("mud", "#9d8b75"),
94 | entry("steel", "#b7b1a6"), // same as metal
95 | entry("glass", "#5a81a0"),
96 | entry("traditional", "#bd8161"), // same as brick
97 | entry("masonry", "#bd8161"), // same as brick
98 | entry("Brick", "#bd8161"), // same as brick
99 | entry("tin", "#b7b1a6"), // same as metal
100 | entry("timber_framing", "#b3b0a9"),
101 | entry("sandstone", "#b4a995"), // same as stone
102 | entry("clay", "#9d8b75") // same as mud
103 | );
104 | private final boolean mergeZ13Buildings;
105 |
106 | public Building(Translations translations, PlanetilerConfig config, Stats stats) {
107 | this.mergeZ13Buildings = config.arguments().getBoolean(
108 | "building_merge_z13",
109 | "building layer: merge nearby buildings at z13",
110 | true
111 | );
112 | }
113 |
114 | @Override
115 | public List preprocessOsmRelation(OsmElement.Relation relation) {
116 | if (relation.hasTag("type", "building")) {
117 | return List.of(new BuildingRelationInfo(relation.id()));
118 | }
119 | return null;
120 | }
121 |
122 | @Override
123 | public void process(Tables.OsmBuildingPolygon element, FeatureCollector features) {
124 | Boolean hide3d = null;
125 | var relations = element.source().relationInfo(BuildingRelationInfo.class);
126 | for (var relation : relations) {
127 | if ("outline".equals(relation.role())) {
128 | hide3d = true;
129 | break;
130 | }
131 | }
132 |
133 | String color = element.colour();
134 | if (color == null && element.material() != null) {
135 | color = MATERIAL_COLORS.get(element.material());
136 | }
137 | if (color != null) {
138 | color = color.toLowerCase(Locale.ROOT);
139 | }
140 |
141 | Double height = coalesce(
142 | parseDoubleOrNull(element.height()),
143 | parseDoubleOrNull(element.buildingheight())
144 | );
145 | Double minHeight = coalesce(
146 | parseDoubleOrNull(element.minHeight()),
147 | parseDoubleOrNull(element.buildingminHeight())
148 | );
149 | Double levels = coalesce(
150 | parseDoubleOrNull(element.levels()),
151 | parseDoubleOrNull(element.buildinglevels())
152 | );
153 | Double minLevels = coalesce(
154 | parseDoubleOrNull(element.minLevel()),
155 | parseDoubleOrNull(element.buildingminLevel())
156 | );
157 |
158 | int renderHeight = (int) Math.ceil(height != null ? height : levels != null ? (levels * 3.66) : 5);
159 | int renderMinHeight = (int) Math.floor(minHeight != null ? minHeight : minLevels != null ? (minLevels * 3.66) : 0);
160 |
161 | if (renderHeight < 3660 && renderMinHeight < 3660) {
162 | var feature = features.polygon(LAYER_NAME).setBufferPixels(BUFFER_SIZE)
163 | .setMinZoom(13)
164 | .setMinPixelSize(2)
165 | .setAttrWithMinzoom(Fields.RENDER_HEIGHT, renderHeight, 14)
166 | .setAttrWithMinzoom(Fields.RENDER_MIN_HEIGHT, renderMinHeight, 14)
167 | .setAttrWithMinzoom(Fields.COLOUR, color, 14)
168 | .setAttrWithMinzoom(Fields.HIDE_3D, hide3d, 14)
169 | .setSortKey(renderHeight);
170 | if (mergeZ13Buildings) {
171 | feature
172 | .setMinPixelSize(0.1)
173 | .setPixelTolerance(0.25);
174 | }
175 | }
176 | }
177 |
178 | @Override
179 | public List postProcess(int zoom,
180 | List items) throws GeometryException {
181 | return (mergeZ13Buildings && zoom == 13) ?
182 | FeatureMerge.mergeNearbyPolygons(items, 4, 4, 0.5, 0.5) :
183 | // reduces the size of some heavy z14 tiles with many small buildings by 60% or more
184 | FeatureMerge.mergeMultiPolygon(items);
185 | }
186 |
187 | private record BuildingRelationInfo(long id) implements OsmRelationInfo {
188 |
189 | @Override
190 | public long estimateMemoryUsageBytes() {
191 | return CLASS_HEADER_BYTES + MemoryEstimator.estimateSizeLong(id);
192 | }
193 | }
194 | }
195 |
--------------------------------------------------------------------------------
/src/main/java/org/openmaptiles/layers/Housenumber.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) 2024, MapTiler.com & OpenMapTiles contributors.
3 | All rights reserved.
4 |
5 | Code license: BSD 3-Clause License
6 |
7 | Redistribution and use in source and binary forms, with or without
8 | modification, are permitted provided that the following conditions are met:
9 |
10 | * Redistributions of source code must retain the above copyright notice, this
11 | list of conditions and the following disclaimer.
12 |
13 | * Redistributions in binary form must reproduce the above copyright notice,
14 | this list of conditions and the following disclaimer in the documentation
15 | and/or other materials provided with the distribution.
16 |
17 | * Neither the name of the copyright holder nor the names of its
18 | contributors may be used to endorse or promote products derived from
19 | this software without specific prior written permission.
20 |
21 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
22 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
24 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
25 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
27 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
28 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
29 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 |
32 | Design license: CC-BY 4.0
33 |
34 | See https://github.com/openmaptiles/openmaptiles/blob/master/LICENSE.md for details on usage
35 | */
36 | package org.openmaptiles.layers;
37 |
38 | import com.onthegomap.planetiler.FeatureCollector;
39 | import com.onthegomap.planetiler.FeatureMerge;
40 | import com.onthegomap.planetiler.ForwardingProfile;
41 | import com.onthegomap.planetiler.VectorTile;
42 | import com.onthegomap.planetiler.config.PlanetilerConfig;
43 | import com.onthegomap.planetiler.geo.GeometryException;
44 | import com.onthegomap.planetiler.stats.Stats;
45 | import com.onthegomap.planetiler.util.Translations;
46 | import java.util.Arrays;
47 | import java.util.Comparator;
48 | import java.util.List;
49 | import java.util.function.Predicate;
50 | import java.util.regex.Matcher;
51 | import java.util.regex.Pattern;
52 | import java.util.stream.Collectors;
53 | import org.openmaptiles.generated.OpenMapTilesSchema;
54 | import org.openmaptiles.generated.Tables;
55 | import org.openmaptiles.util.Utils;
56 | import org.slf4j.Logger;
57 | import org.slf4j.LoggerFactory;
58 |
59 | /**
60 | * Defines the logic for generating map elements in the {@code housenumber} layer from source features.
61 | *
62 | * This class is ported to Java from
63 | * OpenMapTiles housenumber sql
64 | * files.
65 | */
66 | public class Housenumber implements
67 | OpenMapTilesSchema.Housenumber,
68 | Tables.OsmHousenumberPoint.Handler,
69 | ForwardingProfile.LayerPostProcessor {
70 |
71 | private static final Logger LOGGER = LoggerFactory.getLogger(Housenumber.class);
72 | private static final String OSM_SEPARATOR = ";";
73 | private static final String DISPLAY_SEPARATOR = "–";
74 | private static final Pattern NO_CONVERSION_PATTERN = Pattern.compile("[^0-9;]");
75 | private static final String TEMP_PARTITION = "_partition";
76 | private static final String TEMP_HAS_NAME = "_has_name";
77 | private static final Comparator BY_TEMP_HAS_NAME = Comparator
78 | .comparing(i -> (Boolean) i.tags().get(TEMP_HAS_NAME), Boolean::compare);
79 | private final Stats stats;
80 |
81 | public Housenumber(Translations translations, PlanetilerConfig config, Stats stats) {
82 | this.stats = stats;
83 | }
84 |
85 | private static String displayHousenumberNonumeric(List numbers) {
86 | return numbers.getFirst()
87 | .concat(DISPLAY_SEPARATOR)
88 | .concat(numbers.getLast());
89 | }
90 |
91 | protected static String displayHousenumber(String housenumber) {
92 | if (!housenumber.contains(OSM_SEPARATOR)) {
93 | return housenumber;
94 | }
95 |
96 | List numbers = Arrays.stream(housenumber.split(OSM_SEPARATOR))
97 | .map(String::trim)
98 | .filter(Predicate.not(String::isEmpty))
99 | .toList();
100 | if (numbers.isEmpty()) {
101 | // not much to do with strange/invalid entries like "3;" or ";" etc.
102 | return housenumber;
103 | }
104 |
105 | Matcher matcher = NO_CONVERSION_PATTERN.matcher(housenumber);
106 | if (matcher.find()) {
107 | return displayHousenumberNonumeric(numbers);
108 | }
109 |
110 | // numeric display house number
111 | var statistics = numbers.stream()
112 | .collect(Collectors.summarizingLong(Long::parseUnsignedLong));
113 | return String.valueOf(statistics.getMin())
114 | .concat(DISPLAY_SEPARATOR)
115 | .concat(String.valueOf(statistics.getMax()));
116 | }
117 |
118 | @Override
119 | public void process(Tables.OsmHousenumberPoint element, FeatureCollector features) {
120 | String housenumber;
121 | try {
122 | housenumber = displayHousenumber(element.housenumber());
123 | } catch (NumberFormatException e) {
124 | // should not be happening (thanks to NO_CONVERSION_PATTERN) but ...
125 | stats.dataError("housenumber_range");
126 | LOGGER.warn("Failed to convert housenumber range: {}", element.housenumber());
127 | housenumber = element.housenumber();
128 | }
129 |
130 | String partition = Utils.coalesce(element.street(), "")
131 | .concat(Utils.coalesce(element.blockNumber(), ""))
132 | .concat(housenumber);
133 | Boolean hasName = element.hasName() == null ? Boolean.FALSE : !element.hasName().isEmpty();
134 |
135 | features.centroidIfConvex(LAYER_NAME)
136 | .setBufferPixels(BUFFER_SIZE)
137 | .setAttr(Fields.HOUSENUMBER, housenumber)
138 | .setAttr(TEMP_PARTITION, partition)
139 | .setAttr(TEMP_HAS_NAME, hasName)
140 | .setMinZoom(14);
141 | }
142 |
143 | @Override
144 | public List postProcess(int zoom, List list) throws GeometryException {
145 | // remove duplicate house numbers, features without name tag are prioritized
146 | var items = list.stream()
147 | .collect(Collectors.groupingBy(f -> f.tags().get(TEMP_PARTITION)))
148 | .values().stream()
149 | .flatMap(
150 | g -> g.stream().min(BY_TEMP_HAS_NAME).stream()
151 | )
152 | .toList();
153 |
154 | // remove temporary attributes
155 | for (var item : items) {
156 | item.tags().remove(TEMP_HAS_NAME);
157 | item.tags().remove(TEMP_PARTITION);
158 | }
159 |
160 | // reduces the size of some heavy z14 tiles with many repeated housenumber values by 60% or more
161 | return FeatureMerge.mergeMultiPoint(items);
162 | }
163 | }
164 |
--------------------------------------------------------------------------------
/src/main/java/org/openmaptiles/layers/Landcover.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) 2023, MapTiler.com & OpenMapTiles contributors.
3 | All rights reserved.
4 |
5 | Code license: BSD 3-Clause License
6 |
7 | Redistribution and use in source and binary forms, with or without
8 | modification, are permitted provided that the following conditions are met:
9 |
10 | * Redistributions of source code must retain the above copyright notice, this
11 | list of conditions and the following disclaimer.
12 |
13 | * Redistributions in binary form must reproduce the above copyright notice,
14 | this list of conditions and the following disclaimer in the documentation
15 | and/or other materials provided with the distribution.
16 |
17 | * Neither the name of the copyright holder nor the names of its
18 | contributors may be used to endorse or promote products derived from
19 | this software without specific prior written permission.
20 |
21 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
22 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
24 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
25 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
27 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
28 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
29 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 |
32 | Design license: CC-BY 4.0
33 |
34 | See https://github.com/openmaptiles/openmaptiles/blob/master/LICENSE.md for details on usage
35 | */
36 | package org.openmaptiles.layers;
37 |
38 | import com.onthegomap.planetiler.FeatureCollector;
39 | import com.onthegomap.planetiler.FeatureMerge;
40 | import com.onthegomap.planetiler.ForwardingProfile;
41 | import com.onthegomap.planetiler.VectorTile;
42 | import com.onthegomap.planetiler.config.PlanetilerConfig;
43 | import com.onthegomap.planetiler.expression.MultiExpression;
44 | import com.onthegomap.planetiler.geo.GeometryException;
45 | import com.onthegomap.planetiler.reader.SourceFeature;
46 | import com.onthegomap.planetiler.stats.Stats;
47 | import com.onthegomap.planetiler.util.Translations;
48 | import com.onthegomap.planetiler.util.ZoomFunction;
49 | import java.util.ArrayList;
50 | import java.util.List;
51 | import java.util.Map;
52 | import java.util.Set;
53 | import org.openmaptiles.OpenMapTilesProfile;
54 | import org.openmaptiles.generated.OpenMapTilesSchema;
55 | import org.openmaptiles.generated.Tables;
56 |
57 | /**
58 | * Defines the logic for generating map elements for natural land cover polygons like ice, sand, and forest in the
59 | * {@code landcover} layer from source features.
60 | *
61 | * This class is ported to Java from
62 | * OpenMapTiles landcover sql
63 | * files.
64 | */
65 | public class Landcover implements
66 | OpenMapTilesSchema.Landcover,
67 | OpenMapTilesProfile.NaturalEarthProcessor,
68 | Tables.OsmLandcoverPolygon.Handler,
69 | ForwardingProfile.LayerPostProcessor {
70 |
71 | /*
72 | * Large ice areas come from natural earth and the rest come from OpenStreetMap at higher zoom
73 | * levels. At render-time, postProcess() merges polygons into larger connected area based
74 | * on the number of points in the original area. Since postProcess() only has visibility into
75 | * features on a single tile, process() needs to pass the number of points the original feature
76 | * had through using a temporary "_numpoints" attribute.
77 | */
78 |
79 | public static final ZoomFunction MIN_PIXEL_SIZE_THRESHOLDS = ZoomFunction.fromMaxZoomThresholds(Map.of(
80 | 13, 8,
81 | 10, 4,
82 | 9, 2
83 | ));
84 | private static final String TEMP_NUM_POINTS_ATTR = "_numpoints";
85 | private static final Set WOOD_OR_FOREST = Set.of(
86 | FieldValues.SUBCLASS_WOOD,
87 | FieldValues.SUBCLASS_FOREST
88 | );
89 | private final MultiExpression.Index classMapping;
90 |
91 | public Landcover(Translations translations, PlanetilerConfig config, Stats stats) {
92 | this.classMapping = FieldMappings.Class.index();
93 | }
94 |
95 | private String getClassFromSubclass(String subclass) {
96 | return subclass == null ? null : classMapping.getOrElse(Map.of(Fields.SUBCLASS, subclass), null);
97 | }
98 |
99 | @Override
100 | public void processNaturalEarth(String table, SourceFeature feature,
101 | FeatureCollector features) {
102 | record LandcoverInfo(String subclass, int minzoom, int maxzoom) {}
103 | LandcoverInfo info = switch (table) {
104 | case "ne_110m_glaciated_areas" -> new LandcoverInfo(FieldValues.SUBCLASS_GLACIER, 0, 1);
105 | case "ne_50m_glaciated_areas" -> new LandcoverInfo(FieldValues.SUBCLASS_GLACIER, 2, 4);
106 | case "ne_10m_glaciated_areas" -> new LandcoverInfo(FieldValues.SUBCLASS_GLACIER, 5, 6);
107 | case "ne_50m_antarctic_ice_shelves_polys" -> new LandcoverInfo("ice_shelf", 2, 4);
108 | case "ne_10m_antarctic_ice_shelves_polys" -> new LandcoverInfo("ice_shelf", 5, 6);
109 | default -> null;
110 | };
111 | if (info != null) {
112 | String clazz = getClassFromSubclass(info.subclass);
113 | if (clazz != null) {
114 | features.polygon(LAYER_NAME).setBufferPixels(BUFFER_SIZE)
115 | .setAttr(Fields.CLASS, clazz)
116 | .setAttr(Fields.SUBCLASS, info.subclass)
117 | .setZoomRange(info.minzoom, info.maxzoom);
118 | }
119 | }
120 | }
121 |
122 | @Override
123 | public void process(Tables.OsmLandcoverPolygon element, FeatureCollector features) {
124 | String subclass = element.subclass();
125 | String clazz = getClassFromSubclass(subclass);
126 | if (clazz != null) {
127 | features.polygon(LAYER_NAME).setBufferPixels(BUFFER_SIZE)
128 | .setMinPixelSizeOverrides(MIN_PIXEL_SIZE_THRESHOLDS)
129 | // default is 0.1, this helps reduce size of some heavy z7-10 tiles
130 | .setPixelToleranceBelowZoom(10, 0.25)
131 | .setAttr(Fields.CLASS, clazz)
132 | .setAttr(Fields.SUBCLASS, subclass)
133 | .setNumPointsAttr(TEMP_NUM_POINTS_ATTR)
134 | .setMinZoom(7);
135 | }
136 | }
137 |
138 | @Override
139 | public List postProcess(int zoom, List items) throws GeometryException {
140 | if (zoom < 7 || zoom > 13) {
141 | for (var item : items) {
142 | item.tags().remove(TEMP_NUM_POINTS_ATTR);
143 | }
144 | return items;
145 | } else { // z7-13
146 | // merging only merges polygons with the same attributes, so use this temporary key
147 | // to separate features into layers that will be merged separately
148 | String tempGroupKey = "_group";
149 | List result = new ArrayList<>();
150 | List toMerge = new ArrayList<>();
151 | for (var item : items) {
152 | Map attrs = item.attrs();
153 | Object numPointsObj = attrs.remove(TEMP_NUM_POINTS_ATTR);
154 | Object subclassObj = attrs.get(Fields.SUBCLASS);
155 | if (numPointsObj instanceof Number num && subclassObj instanceof String subclass) {
156 | long numPoints = num.longValue();
157 | if (zoom >= 10) {
158 | if (WOOD_OR_FOREST.contains(subclass) && numPoints < 300) {
159 | attrs.put(tempGroupKey, "<300");
160 | toMerge.add(item);
161 | } else { // don't merge
162 | result.add(item);
163 | }
164 | } else if (zoom >= 8 && zoom <= 9) {
165 | if (WOOD_OR_FOREST.contains(subclass)) {
166 | attrs.put(tempGroupKey, numPoints < 300 ? "<300" : ">300");
167 | toMerge.add(item);
168 | } else { // don't merge
169 | result.add(item);
170 | }
171 | } else { // zoom 7
172 | toMerge.add(item);
173 | }
174 | } else {
175 | result.add(item);
176 | }
177 | }
178 | var merged = FeatureMerge.mergeOverlappingPolygons(toMerge, 4);
179 | for (var item : merged) {
180 | item.tags().remove(tempGroupKey);
181 | }
182 | result.addAll(merged);
183 | return result;
184 | }
185 | }
186 | }
187 |
--------------------------------------------------------------------------------
/src/main/java/org/openmaptiles/layers/Landuse.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) 2024, MapTiler.com & OpenMapTiles contributors.
3 | All rights reserved.
4 |
5 | Code license: BSD 3-Clause License
6 |
7 | Redistribution and use in source and binary forms, with or without
8 | modification, are permitted provided that the following conditions are met:
9 |
10 | * Redistributions of source code must retain the above copyright notice, this
11 | list of conditions and the following disclaimer.
12 |
13 | * Redistributions in binary form must reproduce the above copyright notice,
14 | this list of conditions and the following disclaimer in the documentation
15 | and/or other materials provided with the distribution.
16 |
17 | * Neither the name of the copyright holder nor the names of its
18 | contributors may be used to endorse or promote products derived from
19 | this software without specific prior written permission.
20 |
21 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
22 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
24 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
25 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
27 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
28 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
29 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 |
32 | Design license: CC-BY 4.0
33 |
34 | See https://github.com/openmaptiles/openmaptiles/blob/master/LICENSE.md for details on usage
35 | */
36 | package org.openmaptiles.layers;
37 |
38 | import static org.openmaptiles.util.Utils.coalesce;
39 | import static org.openmaptiles.util.Utils.nullIfEmpty;
40 |
41 | import com.onthegomap.planetiler.FeatureCollector;
42 | import com.onthegomap.planetiler.FeatureMerge;
43 | import com.onthegomap.planetiler.ForwardingProfile;
44 | import com.onthegomap.planetiler.VectorTile;
45 | import com.onthegomap.planetiler.config.PlanetilerConfig;
46 | import com.onthegomap.planetiler.geo.GeometryException;
47 | import com.onthegomap.planetiler.reader.SourceFeature;
48 | import com.onthegomap.planetiler.stats.Stats;
49 | import com.onthegomap.planetiler.util.Parse;
50 | import com.onthegomap.planetiler.util.Translations;
51 | import com.onthegomap.planetiler.util.ZoomFunction;
52 | import java.util.ArrayList;
53 | import java.util.List;
54 | import java.util.Map;
55 | import java.util.Set;
56 | import java.util.TreeMap;
57 | import org.openmaptiles.OpenMapTilesProfile;
58 | import org.openmaptiles.generated.OpenMapTilesSchema;
59 | import org.openmaptiles.generated.Tables;
60 |
61 | /**
62 | * Defines the logic for generating map elements for man-made land use polygons like cemeteries, zoos, and hospitals in
63 | * the {@code landuse} layer from source features.
64 | *
65 | * This class is ported to Java from
66 | * OpenMapTiles landuse sql files.
67 | */
68 | public class Landuse implements
69 | OpenMapTilesSchema.Landuse,
70 | OpenMapTilesProfile.NaturalEarthProcessor,
71 | ForwardingProfile.LayerPostProcessor,
72 | Tables.OsmLandusePolygon.Handler {
73 |
74 | private static final ZoomFunction MIN_PIXEL_SIZE_THRESHOLDS = ZoomFunction.fromMaxZoomThresholds(Map.of(
75 | 13, 4,
76 | 7, 2,
77 | 6, 1
78 | ));
79 | private static final TreeMap MINDIST_AND_BUFFER_SIZES = new TreeMap<>(Map.of(
80 | 5, 0.1,
81 | // there is quite huge jump between Z5:NE and Z6:OSM => bigger generalization needed to make the transition more smooth
82 | 6, 0.5,
83 | 7, 0.25,
84 | 8, 0.125,
85 | Integer.MAX_VALUE, 0.1
86 | ));
87 | private static final Set Z6_CLASSES = Set.of(
88 | FieldValues.CLASS_RESIDENTIAL,
89 | FieldValues.CLASS_SUBURB,
90 | FieldValues.CLASS_QUARTER,
91 | FieldValues.CLASS_NEIGHBOURHOOD
92 | );
93 |
94 | public Landuse(Translations translations, PlanetilerConfig config, Stats stats) {}
95 |
96 | @Override
97 | public void processNaturalEarth(String table, SourceFeature feature, FeatureCollector features) {
98 | if ("ne_50m_urban_areas".equals(table)) {
99 | Double scalerank = Parse.parseDoubleOrNull(feature.getTag("scalerank"));
100 | int minzoom = (scalerank != null && scalerank <= 2) ? 4 : 5;
101 | features.polygon(LAYER_NAME).setBufferPixels(BUFFER_SIZE)
102 | .setAttr(Fields.CLASS, FieldValues.CLASS_RESIDENTIAL)
103 | .setZoomRange(minzoom, 5);
104 | }
105 | }
106 |
107 | @Override
108 | public void process(Tables.OsmLandusePolygon element, FeatureCollector features) {
109 | String clazz = coalesce(
110 | nullIfEmpty(element.landuse()),
111 | nullIfEmpty(element.amenity()),
112 | nullIfEmpty(element.leisure()),
113 | nullIfEmpty(element.tourism()),
114 | nullIfEmpty(element.place()),
115 | nullIfEmpty(element.waterway())
116 | );
117 | if (clazz != null) {
118 | if ("grave_yard".equals(clazz)) {
119 | clazz = FieldValues.CLASS_CEMETERY;
120 | }
121 | var feature = features.polygon(LAYER_NAME).setBufferPixels(BUFFER_SIZE)
122 | .setAttr(Fields.CLASS, clazz)
123 | .setMinZoom(Z6_CLASSES.contains(clazz) ? 6 : 9);
124 | if (FieldValues.CLASS_RESIDENTIAL.equals(clazz)) {
125 | feature
126 | .setMinPixelSize(0.1)
127 | .setPixelTolerance(0.25);
128 | } else {
129 | feature
130 | .setMinPixelSizeOverrides(MIN_PIXEL_SIZE_THRESHOLDS);
131 | }
132 | }
133 | }
134 |
135 | @Override
136 | public List postProcess(int zoom,
137 | List items) throws GeometryException {
138 | List toMerge = new ArrayList<>();
139 | List result = new ArrayList<>();
140 | for (var item : items) {
141 | if (FieldValues.CLASS_RESIDENTIAL.equals(item.tags().get(Fields.CLASS))) {
142 | toMerge.add(item);
143 | } else {
144 | result.add(item);
145 | }
146 | }
147 | List merged;
148 | if (zoom <= 12) {
149 | double minDistAndBuffer = MINDIST_AND_BUFFER_SIZES.ceilingEntry(zoom).getValue();
150 | merged = FeatureMerge.mergeNearbyPolygons(toMerge, 1, 1, minDistAndBuffer, minDistAndBuffer);
151 | } else {
152 | // reduces size of some heavy z13-14 tiles with lots of small polygons
153 | merged = FeatureMerge.mergeMultiPolygon(toMerge);
154 | }
155 | result.addAll(merged);
156 | return result;
157 | }
158 | }
159 |
--------------------------------------------------------------------------------
/src/main/java/org/openmaptiles/layers/MountainPeak.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) 2021, MapTiler.com & OpenMapTiles contributors.
3 | All rights reserved.
4 |
5 | Code license: BSD 3-Clause License
6 |
7 | Redistribution and use in source and binary forms, with or without
8 | modification, are permitted provided that the following conditions are met:
9 |
10 | * Redistributions of source code must retain the above copyright notice, this
11 | list of conditions and the following disclaimer.
12 |
13 | * Redistributions in binary form must reproduce the above copyright notice,
14 | this list of conditions and the following disclaimer in the documentation
15 | and/or other materials provided with the distribution.
16 |
17 | * Neither the name of the copyright holder nor the names of its
18 | contributors may be used to endorse or promote products derived from
19 | this software without specific prior written permission.
20 |
21 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
22 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
24 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
25 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
27 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
28 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
29 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 |
32 | Design license: CC-BY 4.0
33 |
34 | See https://github.com/openmaptiles/openmaptiles/blob/master/LICENSE.md for details on usage
35 | */
36 | package org.openmaptiles.layers;
37 |
38 | import static org.openmaptiles.util.Utils.elevationTags;
39 | import static org.openmaptiles.util.Utils.nullIfEmpty;
40 |
41 | import com.carrotsearch.hppc.LongIntMap;
42 | import com.onthegomap.planetiler.FeatureCollector;
43 | import com.onthegomap.planetiler.ForwardingProfile;
44 | import com.onthegomap.planetiler.VectorTile;
45 | import com.onthegomap.planetiler.collection.Hppc;
46 | import com.onthegomap.planetiler.config.PlanetilerConfig;
47 | import com.onthegomap.planetiler.geo.GeometryException;
48 | import com.onthegomap.planetiler.reader.SourceFeature;
49 | import com.onthegomap.planetiler.stats.Stats;
50 | import com.onthegomap.planetiler.util.Parse;
51 | import com.onthegomap.planetiler.util.Translations;
52 | import java.util.List;
53 | import java.util.concurrent.atomic.AtomicBoolean;
54 | import org.locationtech.jts.geom.Geometry;
55 | import org.locationtech.jts.geom.Point;
56 | import org.locationtech.jts.geom.prep.PreparedGeometry;
57 | import org.locationtech.jts.geom.prep.PreparedGeometryFactory;
58 | import org.openmaptiles.OpenMapTilesProfile;
59 | import org.openmaptiles.generated.OpenMapTilesSchema;
60 | import org.openmaptiles.generated.Tables;
61 | import org.openmaptiles.util.OmtLanguageUtils;
62 | import org.slf4j.Logger;
63 | import org.slf4j.LoggerFactory;
64 |
65 | /**
66 | * Defines the logic for generating map elements for mountain peak label points in the {@code mountain_peak} layer from
67 | * source features.
68 | *
69 | * This class is ported to Java from
70 | * OpenMapTiles mountain_peak
71 | * sql files.
72 | */
73 | public class MountainPeak implements
74 | OpenMapTilesProfile.NaturalEarthProcessor,
75 | OpenMapTilesSchema.MountainPeak,
76 | Tables.OsmPeakPoint.Handler,
77 | Tables.OsmMountainLinestring.Handler,
78 | ForwardingProfile.LayerPostProcessor {
79 |
80 | /*
81 | * Mountain peaks come from OpenStreetMap data and are ranked by importance (based on if they
82 | * have a name or wikipedia page) then by elevation. Uses the "label grid" feature to limit
83 | * label density by only taking the top 5 most important mountain peaks within each 100x100px
84 | * square.
85 | */
86 | private static final Logger LOGGER = LoggerFactory.getLogger(MountainPeak.class);
87 |
88 | private final Translations translations;
89 | private final Stats stats;
90 | // keep track of areas that prefer feet to meters to set customary_ft=1 (just U.S.)
91 | private PreparedGeometry unitedStates = null;
92 | private final AtomicBoolean loggedNoUS = new AtomicBoolean(false);
93 |
94 | public MountainPeak(Translations translations, PlanetilerConfig config, Stats stats) {
95 | this.translations = translations;
96 | this.stats = stats;
97 | }
98 |
99 | @Override
100 | public void processNaturalEarth(String table, SourceFeature feature, FeatureCollector features) {
101 | if ("ne_10m_admin_0_countries".equals(table) && feature.hasTag("iso_a2", "US")) {
102 | // multiple threads call this method concurrently, US polygon *should* only be found
103 | // once, but just to be safe synchronize updates to that field
104 | synchronized (this) {
105 | try {
106 | Geometry boundary = feature.polygon();
107 | unitedStates = PreparedGeometryFactory.prepare(boundary);
108 | } catch (GeometryException e) {
109 | LOGGER.error("Failed to get United States Polygon for mountain_peak layer: " + e);
110 | }
111 | }
112 | }
113 | }
114 |
115 | @Override
116 | public void process(Tables.OsmPeakPoint element, FeatureCollector features) {
117 | Double meters = Parse.meters(element.ele());
118 | if (meters != null && Math.abs(meters) < 10_000) {
119 | var feature = features.point(LAYER_NAME)
120 | .setAttr(Fields.CLASS, element.source().getTag("natural"))
121 | .putAttrs(OmtLanguageUtils.getNames(element.source().tags(), translations))
122 | .putAttrs(elevationTags(meters))
123 | .setSortKeyDescending(
124 | meters.intValue() +
125 | (nullIfEmpty(element.wikipedia()) != null ? 10_000 : 0) +
126 | (nullIfEmpty(element.name()) != null ? 10_000 : 0)
127 | )
128 | .setMinZoom(7)
129 | // need to use a larger buffer size to allow enough points through to not cut off
130 | // any label grid squares which could lead to inconsistent label ranks for a feature
131 | // in adjacent tiles. postProcess() will remove anything outside the desired buffer.
132 | .setBufferPixels(100)
133 | .setPointLabelGridSizeAndLimit(13, 100, 5);
134 |
135 | if (peakInAreaUsingFeet(element)) {
136 | feature.setAttr(Fields.CUSTOMARY_FT, 1);
137 | }
138 | }
139 | }
140 |
141 | @Override
142 | public void process(Tables.OsmMountainLinestring element, FeatureCollector features) {
143 | // TODO rank is approximate to sort important/named ridges before others, should switch to labelgrid for linestrings later
144 | int rank = 3 -
145 | (nullIfEmpty(element.wikipedia()) != null ? 1 : 0) -
146 | (nullIfEmpty(element.name()) != null ? 1 : 0);
147 | features.line(LAYER_NAME)
148 | .setAttr(Fields.CLASS, element.source().getTag("natural"))
149 | .setAttr(Fields.RANK, rank)
150 | .putAttrs(OmtLanguageUtils.getNames(element.source().tags(), translations))
151 | .setSortKey(rank)
152 | .setMinZoom(13)
153 | .setBufferPixels(100);
154 | }
155 |
156 | /** Returns true if {@code element} is a point in an area where feet are used insead of meters (the US). */
157 | private boolean peakInAreaUsingFeet(Tables.OsmPeakPoint element) {
158 | if (unitedStates == null) {
159 | if (!loggedNoUS.get() && loggedNoUS.compareAndSet(false, true)) {
160 | LOGGER.warn("No US polygon for inferring mountain_peak customary_ft tag");
161 | }
162 | } else {
163 | try {
164 | Geometry wayGeometry = element.source().worldGeometry();
165 | return unitedStates.intersects(wayGeometry);
166 | } catch (GeometryException e) {
167 | e.log(stats, "omt_mountain_peak_us_test",
168 | "Unable to test mountain_peak against US polygon: " + element.source().id());
169 | }
170 | }
171 | return false;
172 | }
173 |
174 | @Override
175 | public List postProcess(int zoom, List items) {
176 | LongIntMap groupCounts = Hppc.newLongIntHashMap();
177 | for (int i = 0; i < items.size(); i++) {
178 | VectorTile.Feature feature = items.get(i);
179 | int gridrank = groupCounts.getOrDefault(feature.group(), 1);
180 | groupCounts.put(feature.group(), gridrank + 1);
181 | // now that we have accurate ranks, remove anything outside the desired buffer
182 | if (!insideTileBuffer(feature)) {
183 | items.set(i, null);
184 | } else if (!feature.tags().containsKey(Fields.RANK)) {
185 | feature.tags().put(Fields.RANK, gridrank);
186 | }
187 | }
188 | return items;
189 | }
190 |
191 | private static boolean insideTileBuffer(double xOrY) {
192 | return xOrY >= -BUFFER_SIZE && xOrY <= 256 + BUFFER_SIZE;
193 | }
194 |
195 | private boolean insideTileBuffer(VectorTile.Feature feature) {
196 | try {
197 | Geometry geom = feature.geometry().decode();
198 | return !(geom instanceof Point point) || (insideTileBuffer(point.getX()) && insideTileBuffer(point.getY()));
199 | } catch (GeometryException e) {
200 | e.log(stats, "mountain_peak_decode_point", "Error decoding mountain peak point: " + feature.tags());
201 | return false;
202 | }
203 | }
204 | }
205 |
--------------------------------------------------------------------------------
/src/main/java/org/openmaptiles/layers/Park.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) 2024, MapTiler.com & OpenMapTiles contributors.
3 | All rights reserved.
4 |
5 | Code license: BSD 3-Clause License
6 |
7 | Redistribution and use in source and binary forms, with or without
8 | modification, are permitted provided that the following conditions are met:
9 |
10 | * Redistributions of source code must retain the above copyright notice, this
11 | list of conditions and the following disclaimer.
12 |
13 | * Redistributions in binary form must reproduce the above copyright notice,
14 | this list of conditions and the following disclaimer in the documentation
15 | and/or other materials provided with the distribution.
16 |
17 | * Neither the name of the copyright holder nor the names of its
18 | contributors may be used to endorse or promote products derived from
19 | this software without specific prior written permission.
20 |
21 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
22 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
24 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
25 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
27 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
28 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
29 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 |
32 | Design license: CC-BY 4.0
33 |
34 | See https://github.com/openmaptiles/openmaptiles/blob/master/LICENSE.md for details on usage
35 | */
36 | package org.openmaptiles.layers;
37 |
38 | import static com.onthegomap.planetiler.collection.FeatureGroup.SORT_KEY_BITS;
39 | import static org.openmaptiles.util.Utils.coalesce;
40 | import static org.openmaptiles.util.Utils.nullIfEmpty;
41 |
42 | import com.carrotsearch.hppc.LongIntMap;
43 | import com.onthegomap.planetiler.FeatureCollector;
44 | import com.onthegomap.planetiler.FeatureMerge;
45 | import com.onthegomap.planetiler.ForwardingProfile;
46 | import com.onthegomap.planetiler.VectorTile;
47 | import com.onthegomap.planetiler.collection.Hppc;
48 | import com.onthegomap.planetiler.config.PlanetilerConfig;
49 | import com.onthegomap.planetiler.geo.GeoUtils;
50 | import com.onthegomap.planetiler.geo.GeometryException;
51 | import com.onthegomap.planetiler.geo.GeometryType;
52 | import com.onthegomap.planetiler.stats.Stats;
53 | import com.onthegomap.planetiler.util.SortKey;
54 | import com.onthegomap.planetiler.util.Translations;
55 | import java.util.List;
56 | import java.util.Locale;
57 | import org.openmaptiles.generated.OpenMapTilesSchema;
58 | import org.openmaptiles.generated.Tables;
59 | import org.openmaptiles.util.OmtLanguageUtils;
60 |
61 | /**
62 | * Defines the logic for generating map elements for designated parks polygons and their label points in the {@code
63 | * park} layer from source features.
64 | *
65 | * This class is ported to Java from
66 | * OpenMapTiles park sql files.
67 | */
68 | public class Park implements
69 | OpenMapTilesSchema.Park,
70 | Tables.OsmParkPolygon.Handler,
71 | ForwardingProfile.LayerPostProcessor {
72 |
73 | // constants for determining the minimum zoom level for a park label based on its area
74 | private static final double WORLD_AREA_FOR_70K_SQUARE_METERS =
75 | Math.pow(GeoUtils.metersToPixelAtEquator(0, Math.sqrt(70_000)) / 256d, 2);
76 | private static final double LOG2 = Math.log(2);
77 | private static final double SMALLEST_PARK_WORLD_AREA = Math.pow(4, -26); // 2^14 tiles, 2^12 pixels per tile
78 |
79 | private final Translations translations;
80 | private final Stats stats;
81 |
82 | public Park(Translations translations, PlanetilerConfig config, Stats stats) {
83 | this.stats = stats;
84 | this.translations = translations;
85 | }
86 |
87 | @Override
88 | public void process(Tables.OsmParkPolygon element, FeatureCollector features) {
89 | String protectionTitle = element.protectionTitle();
90 | if (protectionTitle != null) {
91 | protectionTitle = protectionTitle.replace(' ', '_').toLowerCase(Locale.ROOT);
92 | }
93 | String clazz = coalesce(
94 | nullIfEmpty(protectionTitle),
95 | nullIfEmpty(element.boundary()),
96 | nullIfEmpty(element.leisure())
97 | );
98 |
99 | // park shape
100 | var outline = features.polygon(LAYER_NAME).setBufferPixels(BUFFER_SIZE)
101 | .setAttrWithMinzoom(Fields.CLASS, clazz, 5)
102 | .setMinPixelSize(2)
103 | .setMinZoom(4);
104 |
105 | // park name label point (if it has one)
106 | if (element.name() != null) {
107 | try {
108 | double area = element.source().area();
109 | int minzoom = getMinZoomForArea(area);
110 |
111 | var names = OmtLanguageUtils.getNamesWithoutTranslations(element.source().tags());
112 |
113 | outline.putAttrsWithMinzoom(names, 5);
114 |
115 | features.pointOnSurface(LAYER_NAME).setBufferPixels(256)
116 | .setAttr(Fields.CLASS, clazz)
117 | .putAttrs(names)
118 | .putAttrs(OmtLanguageUtils.getNames(element.source().tags(), translations))
119 | .setPointLabelGridPixelSize(14, 100)
120 | .setSortKey(SortKey
121 | .orderByTruesFirst("national_park".equals(clazz))
122 | .thenByTruesFirst(element.source().hasTag("wikipedia") || element.source().hasTag("wikidata"))
123 | .thenByLog(area, 1d, SMALLEST_PARK_WORLD_AREA, 1 << (SORT_KEY_BITS - 2) - 1)
124 | .get()
125 | ).setMinZoom(minzoom);
126 | } catch (GeometryException e) {
127 | e.log(stats, "omt_park_area", "Unable to get park area for " + element.source().id());
128 | }
129 | }
130 | }
131 |
132 | private int getMinZoomForArea(double area) {
133 | // sql filter: area > 70000*2^(20-zoom_level)
134 | // simplifies to: zoom_level > 20 - log(area / 70000) / log(2)
135 | int minzoom = (int) Math.floor(20 - Math.log(area / WORLD_AREA_FOR_70K_SQUARE_METERS) / LOG2);
136 | minzoom = Math.clamp(minzoom, 5, 14);
137 | return minzoom;
138 | }
139 |
140 | @Override
141 | public List postProcess(int zoom, List items) throws GeometryException {
142 | // infer the "rank" attribute from point ordering within each label grid square
143 | LongIntMap counts = Hppc.newLongIntHashMap();
144 | for (VectorTile.Feature feature : items) {
145 | if (feature.geometry().geomType() == GeometryType.POINT && feature.hasGroup()) {
146 | int count = counts.getOrDefault(feature.group(), 0) + 1;
147 | feature.tags().put("rank", count);
148 | counts.put(feature.group(), count);
149 | }
150 | }
151 | if (zoom <= 4) {
152 | items = FeatureMerge.mergeOverlappingPolygons(items, 0);
153 | }
154 | return items;
155 | }
156 | }
157 |
--------------------------------------------------------------------------------
/src/main/java/org/openmaptiles/util/OmtLanguageUtils.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) 2021, MapTiler.com & OpenMapTiles contributors.
3 | All rights reserved.
4 |
5 | Code license: BSD 3-Clause License
6 |
7 | Redistribution and use in source and binary forms, with or without
8 | modification, are permitted provided that the following conditions are met:
9 |
10 | * Redistributions of source code must retain the above copyright notice, this
11 | list of conditions and the following disclaimer.
12 |
13 | * Redistributions in binary form must reproduce the above copyright notice,
14 | this list of conditions and the following disclaimer in the documentation
15 | and/or other materials provided with the distribution.
16 |
17 | * Neither the name of the copyright holder nor the names of its
18 | contributors may be used to endorse or promote products derived from
19 | this software without specific prior written permission.
20 |
21 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
22 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
24 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
25 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
27 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
28 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
29 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 |
32 | Design license: CC-BY 4.0
33 |
34 | See https://github.com/openmaptiles/openmaptiles/blob/master/LICENSE.md for details on usage
35 | */
36 | package org.openmaptiles.util;
37 |
38 | import static com.onthegomap.planetiler.util.LanguageUtils.*;
39 | import static org.openmaptiles.util.Utils.coalesce;
40 |
41 | import com.onthegomap.planetiler.util.LanguageUtils;
42 | import com.onthegomap.planetiler.util.Translations;
43 | import java.util.HashMap;
44 | import java.util.Map;
45 | import java.util.stream.Stream;
46 |
47 | /**
48 | * Utilities to extract common name fields (name, name_en, name_de, name:latin, name:nonlatin, name_int) that the
49 | * OpenMapTiles schema uses across any map element with a name.
50 | *
51 | * Ported from
52 | * openmaptiles-tools.
53 | */
54 | public class OmtLanguageUtils {
55 | /**
56 | * Returns a map with default name attributes (name, name_en, name_de, name:latin, name:nonlatin, name_int) that every
57 | * element should have, derived from name, int_name, name:en, and name:de tags on the input element.
58 | *
59 | *
60 | * - name is the original name value from the element
61 | * - name_en is the original name:en value from the element, or name if missing
62 | * - name_de is the original name:de value from the element, or name/ name_en if missing
63 | * - name:latin is the first of name, int_name, or any name: attribute that contains only latin characters
64 | * - name:nonlatin is any nonlatin part of name if present
65 | * - name_int is the first of int_name name:en name:latin name
66 | *
67 | */
68 | public static Map getNamesWithoutTranslations(Map tags) {
69 | return getNames(tags, null);
70 | }
71 |
72 | /**
73 | * Returns a map with default name attributes that {@link #getNamesWithoutTranslations(Map)} adds, but also
74 | * translations for every language that {@code translations} is configured to handle.
75 | */
76 | public static Map getNames(Map tags, Translations translations) {
77 | Map result = new HashMap<>();
78 |
79 | String name = string(tags.get("name"));
80 | String intName = string(tags.get("int_name"));
81 | String nameEn = string(tags.get("name:en"));
82 | String nameDe = string(tags.get("name:de"));
83 |
84 | boolean isLatin = containsOnlyLatinCharacters(name);
85 | String latin = isLatin ? name :
86 | Stream
87 | .concat(Stream.of(nameEn, intName, nameDe), getAllNameTranslationsBesidesEnglishAndGerman(tags))
88 | .filter(LanguageUtils::containsOnlyLatinCharacters)
89 | .findFirst().orElse(null);
90 | if (latin == null && translations != null && translations.getShouldTransliterate()) {
91 | latin = transliteratedName(tags);
92 | }
93 | String nonLatin = isLatin ? null : removeLatinCharacters(name);
94 | if (coalesce(nonLatin, "").equals(latin)) {
95 | nonLatin = null;
96 | }
97 |
98 | putIfNotEmpty(result, "name", name);
99 | putIfNotEmpty(result, "name_en", coalesce(nameEn, name));
100 | putIfNotEmpty(result, "name_de", coalesce(nameDe, name, nameEn));
101 | putIfNotEmpty(result, "name:latin", latin);
102 | putIfNotEmpty(result, "name:nonlatin", nonLatin);
103 | putIfNotEmpty(result, "name_int", coalesce(
104 | intName,
105 | nameEn,
106 | latin,
107 | name
108 | ));
109 |
110 | if (translations != null) {
111 | translations.addTranslations(result, tags);
112 | }
113 |
114 | return result;
115 | }
116 |
117 | public static String string(Object obj) {
118 | return nullIfEmpty(obj == null ? null : obj.toString());
119 | }
120 |
121 | public static String transliteratedName(Map tags) {
122 | return Translations.transliterate(string(tags.get("name")));
123 | }
124 |
125 | private static Stream getAllNameTranslationsBesidesEnglishAndGerman(Map tags) {
126 | return tags.entrySet().stream()
127 | .filter(e -> !EN_DE_NAME_KEYS.contains(e.getKey()) && VALID_NAME_TAGS.test(e.getKey()))
128 | .map(Map.Entry::getValue)
129 | .map(OmtLanguageUtils::string);
130 | }
131 | }
132 |
--------------------------------------------------------------------------------
/src/main/java/org/openmaptiles/util/Utils.java:
--------------------------------------------------------------------------------
1 | package org.openmaptiles.util;
2 |
3 | import com.onthegomap.planetiler.util.Parse;
4 | import java.util.Map;
5 |
6 | /**
7 | * Common utilities for working with data and the OpenMapTiles schema in {@code layers} implementations.
8 | */
9 | public class Utils {
10 |
11 | public static T coalesce(T a, T b) {
12 | return a != null ? a : b;
13 | }
14 |
15 | public static T coalesce(T a, T b, T c) {
16 | return a != null ? a : b != null ? b : c;
17 | }
18 |
19 | public static T coalesce(T a, T b, T c, T d) {
20 | return a != null ? a : b != null ? b : c != null ? c : d;
21 | }
22 |
23 | public static T coalesce(T a, T b, T c, T d, T e) {
24 | return a != null ? a : b != null ? b : c != null ? c : d != null ? d : e;
25 | }
26 |
27 | public static T coalesce(T a, T b, T c, T d, T e, T f) {
28 | return a != null ? a : b != null ? b : c != null ? c : d != null ? d : e != null ? e : f;
29 | }
30 |
31 | /** Boxes {@code a} into an {@link Integer}, or {@code null} if {@code a} is {@code nullValue}. */
32 | public static Long nullIfLong(long a, long nullValue) {
33 | return a == nullValue ? null : a;
34 | }
35 |
36 | /** Boxes {@code a} into a {@link Long}, or {@code null} if {@code a} is {@code nullValue}. */
37 | public static Integer nullIfInt(int a, int nullValue) {
38 | return a == nullValue ? null : a;
39 | }
40 |
41 | /** Returns {@code a}, or null if {@code a} is "". */
42 | public static String nullIfEmpty(String a) {
43 | return (a == null || a.isEmpty()) ? null : a;
44 | }
45 |
46 | /** Returns true if {@code a} is null, or its {@link Object#toString()} value is "". */
47 | public static boolean nullOrEmpty(Object a) {
48 | return a == null || a.toString().isEmpty();
49 | }
50 |
51 | /** Returns a map with {@code ele} (meters) and {ele_ft} attributes from an elevation in meters. */
52 | public static Map elevationTags(double meters) {
53 | return Map.of(
54 | "ele", (int) Math.round(meters),
55 | "ele_ft", (int) Math.round(meters * 3.2808399)
56 | );
57 | }
58 |
59 | /**
60 | * Returns a map with {@code ele} (meters) and {ele_ft} attributes from an elevation string in meters, if {@code
61 | * meters} can be parsed as a valid number.
62 | */
63 | public static Map elevationTags(String meters) {
64 | Double ele = Parse.meters(meters);
65 | return ele == null ? Map.of() : elevationTags(ele);
66 | }
67 |
68 | /** Returns "bridge" or "tunnel" string used for "brunnel" attribute by OpenMapTiles schema. */
69 | public static String brunnel(boolean isBridge, boolean isTunnel) {
70 | return brunnel(isBridge, isTunnel, false);
71 | }
72 |
73 | /** Returns "bridge" or "tunnel" or "ford" string used for "brunnel" attribute by OpenMapTiles schema. */
74 | public static String brunnel(boolean isBridge, boolean isTunnel, boolean isFord) {
75 | return isBridge ? "bridge" : isTunnel ? "tunnel" : isFord ? "ford" : null;
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/src/main/java/org/openmaptiles/util/VerifyMonaco.java:
--------------------------------------------------------------------------------
1 | package org.openmaptiles.util;
2 |
3 | import com.onthegomap.planetiler.mbtiles.Mbtiles;
4 | import com.onthegomap.planetiler.mbtiles.Verify;
5 | import java.io.IOException;
6 | import java.nio.file.Path;
7 | import java.util.Map;
8 | import org.locationtech.jts.geom.Envelope;
9 | import org.locationtech.jts.geom.LineString;
10 | import org.locationtech.jts.geom.Point;
11 | import org.locationtech.jts.geom.Polygon;
12 |
13 | /**
14 | * A utility to check the contents of an mbtiles file generated for Monaco.
15 | */
16 | public class VerifyMonaco {
17 |
18 | public static final Envelope MONACO_BOUNDS = new Envelope(7.40921, 7.44864, 43.72335, 43.75169);
19 |
20 | /**
21 | * Returns a verification result with a basic set of checks against an openmaptiles map built from an extract for
22 | * Monaco.
23 | */
24 | public static Verify verify(Mbtiles mbtiles) {
25 | Verify verify = Verify.verify(mbtiles);
26 | verify.checkMinFeatureCount(MONACO_BOUNDS, "building", Map.of(), 13, 14, 100, Polygon.class);
27 | verify.checkMinFeatureCount(MONACO_BOUNDS, "transportation", Map.of(), 10, 14, 5, LineString.class);
28 | verify.checkMinFeatureCount(MONACO_BOUNDS, "landcover", Map.of(
29 | "class", "grass",
30 | "subclass", "park"
31 | ), 14, 10, Polygon.class);
32 | verify.checkMinFeatureCount(MONACO_BOUNDS, "water", Map.of("class", "ocean"), 0, 14, 1, Polygon.class);
33 | verify.checkMinFeatureCount(MONACO_BOUNDS, "place", Map.of("class", "country"), 2, 14, 1, Point.class);
34 | return verify;
35 | }
36 |
37 | public static void main(String[] args) throws IOException {
38 | try (var mbtiles = Mbtiles.newReadOnlyDatabase(Path.of(args[0]))) {
39 | var result = verify(mbtiles);
40 | result.print();
41 | result.failIfErrors();
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/test/java/org/openmaptiles/GenerateTest.java:
--------------------------------------------------------------------------------
1 | package org.openmaptiles;
2 |
3 | import static com.onthegomap.planetiler.expression.Expression.*;
4 | import static org.junit.jupiter.api.Assertions.assertEquals;
5 | import static org.junit.jupiter.api.DynamicTest.dynamicTest;
6 |
7 | import com.fasterxml.jackson.databind.JsonNode;
8 | import com.onthegomap.planetiler.expression.Expression;
9 | import com.onthegomap.planetiler.expression.MultiExpression;
10 | import java.util.LinkedHashMap;
11 | import java.util.List;
12 | import java.util.Map;
13 | import java.util.stream.Stream;
14 | import org.junit.jupiter.api.DynamicTest;
15 | import org.junit.jupiter.api.Test;
16 | import org.junit.jupiter.api.TestFactory;
17 |
18 | class GenerateTest {
19 |
20 | @Test
21 | void testParseSimple() {
22 | MultiExpression parsed = Generate.generateFieldMapping(Generate.parseYaml("""
23 | output:
24 | key: value
25 | key2:
26 | - value2
27 | - '%value3%'
28 | """));
29 | assertEquals(MultiExpression.of(List.of(
30 | MultiExpression.entry("output", or(
31 | matchAny("key", "value"),
32 | matchAny("key2", "value2", "%value3%")
33 | ))
34 | )), parsed);
35 | }
36 |
37 | @Test
38 | void testParseAnd() {
39 | MultiExpression parsed = Generate.generateFieldMapping(Generate.parseYaml("""
40 | output:
41 | __AND__:
42 | key1: val1
43 | key2: val2
44 | """));
45 | assertEquals(MultiExpression.of(List.of(
46 | MultiExpression.entry("output", and(
47 | matchAny("key1", "val1"),
48 | matchAny("key2", "val2")
49 | ))
50 | )), parsed);
51 | }
52 |
53 | @Test
54 | void testParseAndWithOthers() {
55 | MultiExpression parsed = Generate.generateFieldMapping(Generate.parseYaml("""
56 | output:
57 | - key0: val0
58 | - __AND__:
59 | key1: val1
60 | key2: val2
61 | """));
62 | assertEquals(MultiExpression.of(List.of(
63 | MultiExpression.entry("output", or(
64 | matchAny("key0", "val0"),
65 | and(
66 | matchAny("key1", "val1"),
67 | matchAny("key2", "val2")
68 | )
69 | ))
70 | )), parsed);
71 | }
72 |
73 | @Test
74 | void testParseAndContainingOthers() {
75 | MultiExpression parsed = Generate.generateFieldMapping(Generate.parseYaml("""
76 | output:
77 | __AND__:
78 | - key1: val1
79 | - __OR__:
80 | key2: val2
81 | key3: val3
82 | """));
83 | assertEquals(MultiExpression.of(List.of(
84 | MultiExpression.entry("output", and(
85 | matchAny("key1", "val1"),
86 | or(
87 | matchAny("key2", "val2"),
88 | matchAny("key3", "val3")
89 | )
90 | ))
91 | )), parsed);
92 | }
93 |
94 | @Test
95 | void testParseContainsKey() {
96 | MultiExpression parsed = Generate.generateFieldMapping(Generate.parseYaml("""
97 | output:
98 | key1: val1
99 | key2:
100 | """));
101 | assertEquals(MultiExpression.of(List.of(
102 | MultiExpression.entry("output", or(
103 | matchAny("key1", "val1"),
104 | matchField("key2")
105 | ))
106 | )), parsed);
107 | }
108 |
109 | @TestFactory
110 | Stream testParseImposm3Mapping() {
111 | record TestCase(String name, String mapping, String require, String reject, Expression expected) {
112 |
113 | TestCase(String mapping, Expression expected) {
114 | this(mapping, mapping, null, null, expected);
115 | }
116 | }
117 | return Stream.of(
118 | new TestCase(
119 | "key: val", matchAny("key", "val")
120 | ),
121 | new TestCase(
122 | "key: [val1, val2]", matchAny("key", "val1", "val2")
123 | ),
124 | new TestCase(
125 | "key: [\"__any__\"]", matchField("key")
126 | ),
127 | new TestCase("reject",
128 | "key: val",
129 | "mustkey: mustval",
130 | null,
131 | and(
132 | matchAny("key", "val"),
133 | matchAny("mustkey", "mustval")
134 | )
135 | ),
136 | new TestCase("require",
137 | "key: val",
138 | null,
139 | "badkey: badval",
140 | and(
141 | matchAny("key", "val"),
142 | not(matchAny("badkey", "badval"))
143 | )
144 | ),
145 | new TestCase("require and reject complex",
146 | """
147 | key: val
148 | key2:
149 | - val1
150 | - val2
151 | """,
152 | """
153 | mustkey: mustval
154 | mustkey2:
155 | - mustval1
156 | - mustval2
157 | """,
158 | """
159 | notkey: notval
160 | notkey2:
161 | - notval1
162 | - notval2
163 | """,
164 | and(
165 | or(
166 | matchAny("key", "val"),
167 | matchAny("key2", "val1", "val2")
168 | ),
169 | matchAny("mustkey", "mustval"),
170 | matchAny("mustkey2", "mustval1", "mustval2"),
171 | not(matchAny("notkey", "notval")),
172 | not(matchAny("notkey2", "notval1", "notval2"))
173 | )
174 | )
175 | ).map(test -> dynamicTest(test.name, () -> {
176 | Expression parsed = Generate
177 | .parseImposm3MappingExpression("point", Generate.parseYaml(test.mapping), new Generate.Imposm3Filters(
178 | Generate.parseYaml(test.reject),
179 | Generate.parseYaml(test.require)
180 | ));
181 | assertEquals(test.expected, parsed.replace(matchType("point"), TRUE).simplify());
182 | }));
183 | }
184 |
185 | @Test
186 | void testTypeMappingTopLevelType() {
187 | Expression parsed = Generate
188 | .parseImposm3MappingExpression("point", Generate.parseYaml("""
189 | key: val
190 | """), new Generate.Imposm3Filters(null, null));
191 | assertEquals(and(
192 | matchAny("key", "val"),
193 | matchType("point")
194 | ), parsed);
195 | }
196 |
197 | @Test
198 | void testTypeMappings() {
199 | Map props = new LinkedHashMap<>();
200 | props.put("points", Generate.parseYaml("""
201 | key: val
202 | """));
203 | props.put("polygons", Generate.parseYaml("""
204 | key2: val2
205 | """));
206 | Expression parsed = Generate
207 | .parseImposm3MappingExpression(new Generate.Imposm3Table(
208 | "geometry",
209 | false,
210 | List.of(),
211 | null,
212 | null,
213 | props,
214 | List.of()
215 | ));
216 | assertEquals(or(
217 | and(
218 | matchAny("key", "val"),
219 | matchType("point")
220 | ),
221 | and(
222 | matchAny("key2", "val2"),
223 | matchType("polygon")
224 | )
225 | ), parsed);
226 | }
227 | }
228 |
--------------------------------------------------------------------------------
/src/test/java/org/openmaptiles/OpenMapTilesProfileTest.java:
--------------------------------------------------------------------------------
1 | package org.openmaptiles;
2 |
3 | import static org.junit.jupiter.api.Assertions.assertFalse;
4 | import static org.junit.jupiter.api.Assertions.assertTrue;
5 |
6 | import com.onthegomap.planetiler.config.PlanetilerConfig;
7 | import com.onthegomap.planetiler.reader.osm.OsmElement;
8 | import com.onthegomap.planetiler.stats.Stats;
9 | import com.onthegomap.planetiler.util.Translations;
10 | import com.onthegomap.planetiler.util.Wikidata;
11 | import java.util.List;
12 | import org.junit.jupiter.api.Test;
13 |
14 | class OpenMapTilesProfileTest {
15 |
16 | private final Wikidata.WikidataTranslations wikidataTranslations = new Wikidata.WikidataTranslations();
17 | private final Translations translations = Translations.defaultProvider(List.of("en", "es", "de"))
18 | .addFallbackTranslationProvider(wikidataTranslations);
19 | private final OpenMapTilesProfile profile = new OpenMapTilesProfile(translations, PlanetilerConfig.defaults(),
20 | Stats.inMemory());
21 |
22 | @Test
23 | void testCaresAboutWikidata() {
24 | var node = new OsmElement.Node(1, 1, 1);
25 | node.setTag("aeroway", "gate");
26 | assertTrue(profile.caresAboutWikidataTranslation(node));
27 |
28 | node.setTag("aeroway", "other");
29 | assertFalse(profile.caresAboutWikidataTranslation(node));
30 | }
31 |
32 | @Test
33 | void testDoesntCareAboutWikidataForRoads() {
34 | var way = new OsmElement.Way(1);
35 | way.setTag("highway", "footway");
36 | assertFalse(profile.caresAboutWikidataTranslation(way));
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/test/java/org/openmaptiles/OpenMapTilesTest.java:
--------------------------------------------------------------------------------
1 | package org.openmaptiles;
2 |
3 | import static com.onthegomap.planetiler.TestUtils.assertContains;
4 | import static com.onthegomap.planetiler.TestUtils.assertFeatureNear;
5 | import static com.onthegomap.planetiler.util.Gzip.gunzip;
6 | import static org.junit.jupiter.api.Assertions.assertEquals;
7 | import static org.junit.jupiter.api.DynamicTest.dynamicTest;
8 |
9 | import com.onthegomap.planetiler.TestUtils;
10 | import com.onthegomap.planetiler.VectorTile;
11 | import com.onthegomap.planetiler.archive.Tile;
12 | import com.onthegomap.planetiler.config.Arguments;
13 | import com.onthegomap.planetiler.mbtiles.Mbtiles;
14 | import com.onthegomap.planetiler.util.FileUtils;
15 | import java.io.IOException;
16 | import java.nio.file.Path;
17 | import java.util.Map;
18 | import java.util.Set;
19 | import java.util.stream.Stream;
20 | import org.junit.jupiter.api.AfterAll;
21 | import org.junit.jupiter.api.Assertions;
22 | import org.junit.jupiter.api.BeforeAll;
23 | import org.junit.jupiter.api.DynamicTest;
24 | import org.junit.jupiter.api.Test;
25 | import org.junit.jupiter.api.TestFactory;
26 | import org.junit.jupiter.api.Timeout;
27 | import org.junit.jupiter.api.io.TempDir;
28 | import org.locationtech.jts.geom.Geometry;
29 | import org.locationtech.jts.geom.LineString;
30 | import org.locationtech.jts.geom.Point;
31 | import org.locationtech.jts.geom.Polygon;
32 | import org.openmaptiles.util.VerifyMonaco;
33 |
34 | /**
35 | * End-to-end tests for OpenMapTiles generation.
36 | *
37 | * Generates an entire map for the smallest openstreetmap extract available (Monaco) and asserts that expected output
38 | * features exist
39 | */
40 | class OpenMapTilesTest {
41 |
42 | @TempDir
43 | static Path tmpDir;
44 | private static Mbtiles mbtiles;
45 |
46 | @BeforeAll
47 | @Timeout(30)
48 | public static void runPlanetiler() throws Exception {
49 | Path dbPath = tmpDir.resolve("output.mbtiles");
50 | var osmPath = TestUtils.extractPathToResource(tmpDir, "monaco-latest.osm.pbf");
51 | var naturalEarthPath = TestUtils.extractPathToResource(tmpDir, "natural_earth_vector.sqlite.zip");
52 | var waterPath = tmpDir.resolve("water");
53 | // windows seems to have trouble closing zip file after reading from it, so extract first instead
54 | FileUtils.unzipResource("/water-polygons-split-3857.zip", waterPath);
55 | OpenMapTilesMain.run(Arguments.of(
56 | // Override input source locations
57 | "osm_path", osmPath,
58 | "natural_earth_path", naturalEarthPath,
59 | "water_polygons_path", waterPath,
60 | // no centerlines in monaco - so fake it out with an empty source
61 | "lake_centerlines_path", waterPath,
62 |
63 | // Override temp dir location
64 | "tmpdir", tmpDir.resolve("tmp"),
65 |
66 | // Override output location
67 | "mbtiles", dbPath
68 | ));
69 |
70 | mbtiles = Mbtiles.newReadOnlyDatabase(dbPath);
71 | }
72 |
73 | @AfterAll
74 | public static void close() throws IOException {
75 | mbtiles.close();
76 | }
77 |
78 | @Test
79 | void testMetadata() {
80 | Map metadata = mbtiles.metadataTable().getAll();
81 | assertEquals("OpenMapTiles", metadata.get("name"));
82 | assertEquals("0", metadata.get("minzoom"));
83 | assertEquals("14", metadata.get("maxzoom"));
84 | assertEquals("baselayer", metadata.get("type"));
85 | assertEquals("pbf", metadata.get("format"));
86 | assertEquals("7.40921,43.72335,7.44864,43.75169", metadata.get("bounds"));
87 | assertEquals("7.42892,43.73752,14", metadata.get("center"));
88 | assertContains("openmaptiles.org", metadata.get("description"));
89 | assertContains("openmaptiles.org", metadata.get("attribution"));
90 | assertContains("www.openstreetmap.org/copyright", metadata.get("attribution"));
91 | }
92 |
93 | @Test
94 | void ensureValidGeometries() throws Exception {
95 | Set parsedTiles = TestUtils.getTiles(mbtiles);
96 | for (var tileEntry : parsedTiles) {
97 | var decoded = VectorTile.decode(gunzip(tileEntry.bytes()));
98 | for (VectorTile.Feature feature : decoded) {
99 | TestUtils.validateGeometry(feature.geometry().decode());
100 | }
101 | }
102 | }
103 |
104 | @Test
105 | void testContainsOceanPolyons() {
106 | assertFeatureNear(mbtiles, "water", Map.of(
107 | "class", "ocean"
108 | ), 7.4484, 43.70783, 0, 14);
109 | }
110 |
111 | @Test
112 | void testContainsCountryName() {
113 | assertFeatureNear(mbtiles, "place", Map.of(
114 | "class", "country",
115 | "iso_a2", "MC",
116 | "name", "Monaco"
117 | ), 7.42769, 43.73235, 2, 14);
118 | }
119 |
120 | @Test
121 | void testContainsSuburb() {
122 | assertFeatureNear(mbtiles, "place", Map.of(
123 | "name", "Les Moneghetti",
124 | "class", "suburb"
125 | ), 7.41746, 43.73638, 11, 14);
126 | }
127 |
128 | @Test
129 | void testContainsBuildings() {
130 | assertFeatureNear(mbtiles, "building", Map.of(), 7.41919, 43.73401, 13, 14);
131 | assertNumFeatures("building", Map.of(), 14, 1316, Polygon.class);
132 | assertNumFeatures("building", Map.of(), 13, 196, Polygon.class);
133 | }
134 |
135 | @Test
136 | void testContainsHousenumber() {
137 | assertFeatureNear(mbtiles, "housenumber", Map.of(
138 | "housenumber", "27"
139 | ), 7.42117, 43.73652, 14, 14);
140 | assertNumFeatures("housenumber", Map.of(), 14, 231, Point.class);
141 | }
142 |
143 | @Test
144 | void testBoundary() {
145 | assertFeatureNear(mbtiles, "boundary", Map.of(
146 | "admin_level", 2L,
147 | "maritime", 1L,
148 | "disputed", 0L
149 | ), 7.41884, 43.72396, 4, 14);
150 | }
151 |
152 | @Test
153 | void testAeroway() {
154 | assertNumFeatures("aeroway", Map.of(
155 | "class", "heliport"
156 | ), 14, 1, Polygon.class);
157 | assertNumFeatures("aeroway", Map.of(
158 | "class", "helipad"
159 | ), 14, 11, Polygon.class);
160 | }
161 |
162 | @Test
163 | void testLandcover() {
164 | assertNumFeatures("landcover", Map.of(
165 | "class", "grass",
166 | "subclass", "park"
167 | ), 14, 20, Polygon.class);
168 | assertNumFeatures("landcover", Map.of(
169 | "class", "grass",
170 | "subclass", "garden"
171 | ), 14, 33, Polygon.class);
172 | }
173 |
174 | @Test
175 | void testPoi() {
176 | assertNumFeatures("poi", Map.of(
177 | "class", "restaurant",
178 | "subclass", "restaurant"
179 | ), 14, 217, Point.class);
180 | assertNumFeatures("poi", Map.of(
181 | "class", "art_gallery",
182 | "subclass", "artwork"
183 | ), 14, 132, Point.class);
184 | }
185 |
186 | @Test
187 | void testLanduse() {
188 | assertNumFeatures("landuse", Map.of(
189 | "class", "residential"
190 | ), 14, 8, Polygon.class);
191 | assertNumFeatures("landuse", Map.of(
192 | "class", "hospital"
193 | ), 14, 4, Polygon.class);
194 | }
195 |
196 | @Test
197 | void testTransportation() {
198 | assertNumFeatures("transportation", Map.of(
199 | "class", "path",
200 | "subclass", "footway"
201 | ), 14, 756, LineString.class);
202 | assertNumFeatures("transportation", Map.of(
203 | "class", "primary"
204 | ), 14, 249, LineString.class);
205 | }
206 |
207 | @Test
208 | void testTransportationName() {
209 | assertNumFeatures("transportation_name", Map.of(
210 | "name", "Boulevard du Larvotto",
211 | "class", "primary"
212 | ), 14, 9, LineString.class);
213 | }
214 |
215 | @Test
216 | void testWaterway() {
217 | assertNumFeatures("waterway", Map.of(
218 | "class", "stream"
219 | ), 14, 6, LineString.class);
220 | }
221 |
222 | @TestFactory
223 | Stream testVerifyChecks() {
224 | return VerifyMonaco.verify(mbtiles).results().stream()
225 | .map(check -> dynamicTest(check.name(), () -> {
226 | check.error().ifPresent(Assertions::fail);
227 | }));
228 | }
229 |
230 | private static void assertNumFeatures(String layer, Map attrs, int zoom,
231 | int expected, Class extends Geometry> clazz) {
232 | TestUtils.assertNumFeatures(mbtiles, layer, zoom, attrs, VerifyMonaco.MONACO_BOUNDS, expected, clazz);
233 | }
234 | }
235 |
--------------------------------------------------------------------------------
/src/test/java/org/openmaptiles/layers/AbstractLayerTest.java:
--------------------------------------------------------------------------------
1 | package org.openmaptiles.layers;
2 |
3 | import static com.onthegomap.planetiler.TestUtils.assertSubmap;
4 | import static com.onthegomap.planetiler.TestUtils.newLineString;
5 | import static com.onthegomap.planetiler.TestUtils.newPoint;
6 | import static com.onthegomap.planetiler.TestUtils.rectangle;
7 | import static org.junit.jupiter.api.Assertions.assertEquals;
8 | import static org.junit.jupiter.api.Assertions.fail;
9 |
10 | import com.onthegomap.planetiler.FeatureCollector;
11 | import com.onthegomap.planetiler.TestUtils;
12 | import com.onthegomap.planetiler.VectorTile;
13 | import com.onthegomap.planetiler.config.PlanetilerConfig;
14 | import com.onthegomap.planetiler.geo.GeoUtils;
15 | import com.onthegomap.planetiler.geo.GeometryException;
16 | import com.onthegomap.planetiler.reader.SimpleFeature;
17 | import com.onthegomap.planetiler.reader.SourceFeature;
18 | import com.onthegomap.planetiler.reader.osm.OsmReader;
19 | import com.onthegomap.planetiler.reader.osm.OsmRelationInfo;
20 | import com.onthegomap.planetiler.stats.Stats;
21 | import com.onthegomap.planetiler.util.Translations;
22 | import com.onthegomap.planetiler.util.Wikidata;
23 | import java.util.Arrays;
24 | import java.util.Comparator;
25 | import java.util.HashMap;
26 | import java.util.List;
27 | import java.util.Map;
28 | import java.util.stream.StreamSupport;
29 | import org.openmaptiles.OpenMapTilesProfile;
30 | import org.openmaptiles.util.Utils;
31 |
32 | public abstract class AbstractLayerTest {
33 |
34 | final Wikidata.WikidataTranslations wikidataTranslations = new Wikidata.WikidataTranslations();
35 | final Translations translations = Translations.defaultProvider(List.of("en", "es", "de"))
36 | .addFallbackTranslationProvider(wikidataTranslations);
37 |
38 | final PlanetilerConfig params = PlanetilerConfig.defaults();
39 | final OpenMapTilesProfile profile = new OpenMapTilesProfile(translations, PlanetilerConfig.defaults(),
40 | Stats.inMemory());
41 | final Stats stats = Stats.inMemory();
42 | final FeatureCollector.Factory featureCollectorFactory = new FeatureCollector.Factory(params, stats);
43 |
44 | static void assertFeatures(int zoom, List