2021-08-02 00:26:22 +00:00
|
|
|
/*
|
2022-01-19 10:36:44 +00:00
|
|
|
Copyright (c) 2021, MapTiler.com & OpenMapTiles contributors.
|
2021-08-02 00:26:22 +00:00
|
|
|
All rights reserved.
|
|
|
|
|
|
|
|
Code license: BSD 3-Clause License
|
|
|
|
|
|
|
|
Redistribution and use in source and binary forms, with or without
|
|
|
|
modification, are permitted provided that the following conditions are met:
|
|
|
|
|
|
|
|
* Redistributions of source code must retain the above copyright notice, this
|
|
|
|
list of conditions and the following disclaimer.
|
|
|
|
|
|
|
|
* Redistributions in binary form must reproduce the above copyright notice,
|
|
|
|
this list of conditions and the following disclaimer in the documentation
|
|
|
|
and/or other materials provided with the distribution.
|
|
|
|
|
|
|
|
* Neither the name of the copyright holder nor the names of its
|
|
|
|
contributors may be used to endorse or promote products derived from
|
|
|
|
this software without specific prior written permission.
|
|
|
|
|
|
|
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
|
|
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
|
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
|
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
|
|
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
|
|
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
|
|
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
|
|
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
|
|
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
|
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
|
|
|
|
Design license: CC-BY 4.0
|
|
|
|
|
|
|
|
See https://github.com/openmaptiles/openmaptiles/blob/master/LICENSE.md for details on usage
|
|
|
|
*/
|
2021-12-23 10:42:24 +00:00
|
|
|
package com.onthegomap.planetiler.basemap.layers;
|
2021-06-14 11:04:03 +00:00
|
|
|
|
2021-12-23 10:42:24 +00:00
|
|
|
import static com.onthegomap.planetiler.basemap.util.Utils.*;
|
2022-01-19 10:36:44 +00:00
|
|
|
import static com.onthegomap.planetiler.util.MemoryEstimator.CLASS_HEADER_BYTES;
|
|
|
|
import static com.onthegomap.planetiler.util.MemoryEstimator.POINTER_BYTES;
|
|
|
|
import static com.onthegomap.planetiler.util.MemoryEstimator.estimateSize;
|
|
|
|
import static java.util.Map.entry;
|
2021-07-13 10:09:36 +00:00
|
|
|
|
2021-12-23 10:42:24 +00:00
|
|
|
import com.onthegomap.planetiler.FeatureCollector;
|
|
|
|
import com.onthegomap.planetiler.FeatureMerge;
|
|
|
|
import com.onthegomap.planetiler.VectorTile;
|
|
|
|
import com.onthegomap.planetiler.basemap.BasemapProfile;
|
|
|
|
import com.onthegomap.planetiler.basemap.generated.OpenMapTilesSchema;
|
|
|
|
import com.onthegomap.planetiler.basemap.generated.Tables;
|
|
|
|
import com.onthegomap.planetiler.config.PlanetilerConfig;
|
|
|
|
import com.onthegomap.planetiler.expression.MultiExpression;
|
2022-01-19 10:36:44 +00:00
|
|
|
import com.onthegomap.planetiler.geo.GeoUtils;
|
2021-12-23 10:42:24 +00:00
|
|
|
import com.onthegomap.planetiler.geo.GeometryException;
|
2022-01-19 10:36:44 +00:00
|
|
|
import com.onthegomap.planetiler.reader.SourceFeature;
|
|
|
|
import com.onthegomap.planetiler.reader.osm.OsmElement;
|
|
|
|
import com.onthegomap.planetiler.reader.osm.OsmReader;
|
|
|
|
import com.onthegomap.planetiler.reader.osm.OsmRelationInfo;
|
2021-12-23 10:42:24 +00:00
|
|
|
import com.onthegomap.planetiler.stats.Stats;
|
2022-01-19 10:36:44 +00:00
|
|
|
import com.onthegomap.planetiler.util.MemoryEstimator;
|
2021-12-23 10:42:24 +00:00
|
|
|
import com.onthegomap.planetiler.util.Parse;
|
|
|
|
import com.onthegomap.planetiler.util.Translations;
|
|
|
|
import com.onthegomap.planetiler.util.ZoomFunction;
|
2022-01-19 10:36:44 +00:00
|
|
|
import java.util.ArrayList;
|
|
|
|
import java.util.Collections;
|
|
|
|
import java.util.Comparator;
|
2021-07-13 10:09:36 +00:00
|
|
|
import java.util.List;
|
|
|
|
import java.util.Map;
|
|
|
|
import java.util.Set;
|
2022-01-19 10:36:44 +00:00
|
|
|
import java.util.concurrent.atomic.AtomicBoolean;
|
|
|
|
import java.util.regex.Matcher;
|
|
|
|
import java.util.regex.Pattern;
|
|
|
|
import org.locationtech.jts.geom.Geometry;
|
2021-07-13 10:09:36 +00:00
|
|
|
import org.locationtech.jts.geom.LineString;
|
2022-01-19 10:36:44 +00:00
|
|
|
import org.locationtech.jts.geom.prep.PreparedGeometry;
|
|
|
|
import org.locationtech.jts.geom.prep.PreparedGeometryFactory;
|
|
|
|
import org.slf4j.Logger;
|
|
|
|
import org.slf4j.LoggerFactory;
|
2021-07-13 10:09:36 +00:00
|
|
|
|
2021-08-02 00:26:22 +00:00
|
|
|
/**
|
2021-09-16 09:15:43 +00:00
|
|
|
* Defines the logic for generating map elements for roads, shipways, railroads, and paths in the {@code transportation}
|
|
|
|
* layer from source features.
|
|
|
|
* <p>
|
2022-03-09 02:08:03 +00:00
|
|
|
* This class is ported to Java from
|
|
|
|
* <a href="https://github.com/openmaptiles/openmaptiles/tree/master/layers/transportation">OpenMapTiles transportation
|
|
|
|
* sql files</a>.
|
2021-08-02 00:26:22 +00:00
|
|
|
*/
|
2021-07-13 10:09:36 +00:00
|
|
|
public class Transportation implements
|
|
|
|
OpenMapTilesSchema.Transportation,
|
|
|
|
Tables.OsmAerialwayLinestring.Handler,
|
|
|
|
Tables.OsmHighwayLinestring.Handler,
|
|
|
|
Tables.OsmRailwayLinestring.Handler,
|
|
|
|
Tables.OsmShipwayLinestring.Handler,
|
|
|
|
Tables.OsmHighwayPolygon.Handler,
|
2022-01-19 10:36:44 +00:00
|
|
|
BasemapProfile.NaturalEarthProcessor,
|
2021-10-20 01:57:47 +00:00
|
|
|
BasemapProfile.FeaturePostProcessor,
|
2022-01-19 10:36:44 +00:00
|
|
|
BasemapProfile.OsmRelationPreprocessor,
|
2021-10-20 01:57:47 +00:00
|
|
|
BasemapProfile.IgnoreWikidata {
|
2021-07-13 10:09:36 +00:00
|
|
|
|
2021-09-16 09:15:43 +00:00
|
|
|
/*
|
|
|
|
* Generates the shape for roads, trails, ferries, railways with detailed
|
|
|
|
* attributes for rendering, but not any names. The transportation_name
|
|
|
|
* layer includes names, but less detailed attributes.
|
|
|
|
*/
|
2021-06-14 11:04:03 +00:00
|
|
|
|
2022-01-19 10:36:44 +00:00
|
|
|
private static final Logger LOGGER = LoggerFactory.getLogger(Transportation.class);
|
|
|
|
private static final Pattern GREAT_BRITAIN_REF_NETWORK_PATTERN = Pattern.compile("^[AM][0-9AM()]+");
|
2021-09-16 09:15:43 +00:00
|
|
|
private static final MultiExpression.Index<String> classMapping = FieldMappings.Class.index();
|
2021-07-13 10:09:36 +00:00
|
|
|
private static final Set<String> RAILWAY_RAIL_VALUES = Set.of(
|
|
|
|
FieldValues.SUBCLASS_RAIL,
|
|
|
|
FieldValues.SUBCLASS_NARROW_GAUGE,
|
|
|
|
FieldValues.SUBCLASS_PRESERVED,
|
|
|
|
FieldValues.SUBCLASS_FUNICULAR
|
|
|
|
);
|
|
|
|
private static final Set<String> RAILWAY_TRANSIT_VALUES = Set.of(
|
|
|
|
FieldValues.SUBCLASS_SUBWAY,
|
|
|
|
FieldValues.SUBCLASS_LIGHT_RAIL,
|
|
|
|
FieldValues.SUBCLASS_MONORAIL,
|
|
|
|
FieldValues.SUBCLASS_TRAM
|
|
|
|
);
|
|
|
|
private static final Set<String> SERVICE_VALUES = Set.of(
|
|
|
|
FieldValues.SERVICE_SPUR,
|
|
|
|
FieldValues.SERVICE_YARD,
|
|
|
|
FieldValues.SERVICE_SIDING,
|
|
|
|
FieldValues.SERVICE_CROSSOVER,
|
|
|
|
FieldValues.SERVICE_DRIVEWAY,
|
|
|
|
FieldValues.SERVICE_ALLEY,
|
|
|
|
FieldValues.SERVICE_PARKING_AISLE
|
|
|
|
);
|
|
|
|
private static final Set<String> SURFACE_UNPAVED_VALUES = Set.of(
|
|
|
|
"unpaved", "compacted", "dirt", "earth", "fine_gravel", "grass", "grass_paver", "gravel", "gravel_turf", "ground",
|
|
|
|
"ice", "mud", "pebblestone", "salt", "sand", "snow", "woodchips"
|
|
|
|
);
|
|
|
|
private static final Set<String> SURFACE_PAVED_VALUES = Set.of(
|
|
|
|
"paved", "asphalt", "cobblestone", "concrete", "concrete:lanes", "concrete:plates", "metal",
|
|
|
|
"paving_stones", "sett", "unhewn_cobblestone", "wood"
|
|
|
|
);
|
2022-01-19 10:36:44 +00:00
|
|
|
private static final Set<String> ACCESS_NO_VALUES = Set.of(
|
|
|
|
"private", "no"
|
|
|
|
);
|
2021-09-10 00:46:20 +00:00
|
|
|
private static final ZoomFunction.MeterToPixelThresholds MIN_LENGTH = ZoomFunction.meterThresholds()
|
2021-07-13 10:09:36 +00:00
|
|
|
.put(7, 50)
|
|
|
|
.put(6, 100)
|
|
|
|
.put(5, 500)
|
|
|
|
.put(4, 1_000);
|
2022-01-19 10:36:44 +00:00
|
|
|
// ORDER BY network_type, network, LENGTH(ref), ref)
|
|
|
|
private static final Comparator<RouteRelation> RELATION_ORDERING = Comparator
|
|
|
|
.<RouteRelation>comparingInt(
|
|
|
|
r -> r.networkType() != null ? r.networkType.ordinal() : Integer.MAX_VALUE)
|
|
|
|
.thenComparing(routeRelation -> coalesce(routeRelation.network(), ""))
|
|
|
|
.thenComparingInt(r -> r.ref().length())
|
|
|
|
.thenComparing(RouteRelation::ref);
|
|
|
|
private final AtomicBoolean loggedNoGb = new AtomicBoolean(false);
|
|
|
|
private final boolean z13Paths;
|
|
|
|
private PreparedGeometry greatBritain = null;
|
2021-09-16 09:15:43 +00:00
|
|
|
private final Map<String, Integer> MINZOOMS;
|
2021-07-18 12:17:58 +00:00
|
|
|
private final Stats stats;
|
2021-12-23 10:42:24 +00:00
|
|
|
private final PlanetilerConfig config;
|
2021-06-16 10:01:39 +00:00
|
|
|
|
2021-12-23 10:42:24 +00:00
|
|
|
public Transportation(Translations translations, PlanetilerConfig config, Stats stats) {
|
2021-09-10 00:46:20 +00:00
|
|
|
this.config = config;
|
2021-07-18 12:17:58 +00:00
|
|
|
this.stats = stats;
|
2022-01-19 10:36:44 +00:00
|
|
|
z13Paths = config.arguments().getBoolean(
|
2021-07-17 09:29:44 +00:00
|
|
|
"transportation_z13_paths",
|
2022-01-19 10:36:44 +00:00
|
|
|
"transportation(_name) layer: show all paths on z13",
|
|
|
|
false
|
2021-07-17 09:29:44 +00:00
|
|
|
);
|
2022-01-19 10:36:44 +00:00
|
|
|
MINZOOMS = Map.ofEntries(
|
|
|
|
entry(FieldValues.CLASS_PATH, z13Paths ? 13 : 14),
|
|
|
|
entry(FieldValues.CLASS_TRACK, 14),
|
|
|
|
entry(FieldValues.CLASS_SERVICE, 13),
|
|
|
|
entry(FieldValues.CLASS_MINOR, 13),
|
|
|
|
entry(FieldValues.CLASS_RACEWAY, 12),
|
|
|
|
entry(FieldValues.CLASS_TERTIARY, 11),
|
|
|
|
entry(FieldValues.CLASS_BUSWAY, 11),
|
|
|
|
entry(FieldValues.CLASS_SECONDARY, 9),
|
|
|
|
entry(FieldValues.CLASS_PRIMARY, 7),
|
|
|
|
entry(FieldValues.CLASS_TRUNK, 5),
|
|
|
|
entry(FieldValues.CLASS_MOTORWAY, 4)
|
2021-07-17 09:29:44 +00:00
|
|
|
);
|
2021-06-16 10:01:39 +00:00
|
|
|
}
|
2021-06-19 20:09:32 +00:00
|
|
|
|
2022-01-19 10:36:44 +00:00
|
|
|
@Override
|
|
|
|
public void processNaturalEarth(String table, SourceFeature feature,
|
|
|
|
FeatureCollector features) {
|
|
|
|
if ("ne_10m_admin_0_countries".equals(table) && feature.hasTag("iso_a2", "GB")) {
|
|
|
|
// multiple threads call this method concurrently, GB polygon *should* only be found
|
|
|
|
// once, but just to be safe synchronize updates to that field
|
|
|
|
synchronized (this) {
|
|
|
|
try {
|
|
|
|
Geometry boundary = feature.polygon().buffer(GeoUtils.metersToPixelAtEquator(0, 10_000) / 256d);
|
|
|
|
greatBritain = PreparedGeometryFactory.prepare(boundary);
|
|
|
|
} catch (GeometryException e) {
|
|
|
|
LOGGER.error("Failed to get Great Britain Polygon: " + e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-09-16 09:15:43 +00:00
|
|
|
/** Returns a value for {@code surface} tag constrained to a small set of known values from raw OSM data. */
|
2021-07-13 10:09:36 +00:00
|
|
|
private static String surface(String value) {
|
|
|
|
return value == null ? null : SURFACE_PAVED_VALUES.contains(value) ? FieldValues.SURFACE_PAVED :
|
|
|
|
SURFACE_UNPAVED_VALUES.contains(value) ? FieldValues.SURFACE_UNPAVED : null;
|
|
|
|
}
|
|
|
|
|
2022-01-19 10:36:44 +00:00
|
|
|
/** Returns a value for {@code access} tag constrained to a small set of known values from raw OSM data. */
|
|
|
|
private static String access(String value) {
|
|
|
|
return value == null ? null : ACCESS_NO_VALUES.contains(value) ? "no" : null;
|
|
|
|
}
|
|
|
|
|
2021-09-16 09:15:43 +00:00
|
|
|
/** Returns a value for {@code service} tag constrained to a small set of known values from raw OSM data. */
|
2021-07-13 10:09:36 +00:00
|
|
|
private static String service(String value) {
|
|
|
|
return (value == null || !SERVICE_VALUES.contains(value)) ? null : value;
|
|
|
|
}
|
|
|
|
|
|
|
|
private static String railwayClass(String value) {
|
|
|
|
return value == null ? null :
|
|
|
|
RAILWAY_RAIL_VALUES.contains(value) ? "rail" :
|
2022-03-09 02:08:03 +00:00
|
|
|
RAILWAY_TRANSIT_VALUES.contains(value) ? "transit" : null;
|
2021-07-13 10:09:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static String highwayClass(String highway, String publicTransport, String construction, String manMade) {
|
|
|
|
return (!nullOrEmpty(highway) || !nullOrEmpty(publicTransport)) ? classMapping.getOrElse(Map.of(
|
|
|
|
"highway", coalesce(highway, ""),
|
|
|
|
"public_transport", coalesce(publicTransport, ""),
|
|
|
|
"construction", coalesce(construction, "")
|
2022-02-08 01:41:02 +00:00
|
|
|
), null) : isBridgeOrPier(manMade) ? manMade : null;
|
2021-07-13 10:09:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static String highwaySubclass(String highwayClass, String publicTransport, String highway) {
|
|
|
|
return FieldValues.CLASS_PATH.equals(highwayClass) ? coalesce(nullIfEmpty(publicTransport), highway) : null;
|
|
|
|
}
|
|
|
|
|
|
|
|
static boolean isFootwayOrSteps(String highway) {
|
|
|
|
return "footway".equals(highway) || "steps".equals(highway);
|
|
|
|
}
|
|
|
|
|
2022-01-19 10:36:44 +00:00
|
|
|
static boolean isLink(String highway) {
|
|
|
|
return highway != null && highway.endsWith("_link");
|
|
|
|
}
|
|
|
|
|
2021-07-13 10:09:36 +00:00
|
|
|
private static boolean isResidentialOrUnclassified(String highway) {
|
|
|
|
return "residential".equals(highway) || "unclassified".equals(highway);
|
|
|
|
}
|
|
|
|
|
2022-01-19 10:36:44 +00:00
|
|
|
private static boolean isDrivewayOrParkingAisle(String service) {
|
|
|
|
return FieldValues.SERVICE_PARKING_AISLE.equals(service) || FieldValues.SERVICE_DRIVEWAY.equals(service);
|
|
|
|
}
|
|
|
|
|
2021-07-13 10:09:36 +00:00
|
|
|
private static boolean isBridgeOrPier(String manMade) {
|
|
|
|
return "bridge".equals(manMade) || "pier".equals(manMade);
|
|
|
|
}
|
|
|
|
|
2022-01-19 10:36:44 +00:00
|
|
|
enum RouteNetwork {
|
|
|
|
|
|
|
|
US_INTERSTATE("us-interstate"),
|
|
|
|
US_HIGHWAY("us-highway"),
|
|
|
|
US_STATE("us-state"),
|
|
|
|
CA_TRANSCANADA("ca-transcanada"),
|
|
|
|
GB_MOTORWAY("gb-motorway"),
|
|
|
|
GB_TRUNK("gb-trunk");
|
|
|
|
|
|
|
|
final String name;
|
|
|
|
|
|
|
|
RouteNetwork(String name) {
|
|
|
|
this.name = name;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public List<OsmRelationInfo> preprocessOsmRelation(OsmElement.Relation relation) {
|
|
|
|
if (relation.hasTag("route", "road", "hiking")) {
|
|
|
|
RouteNetwork networkType = null;
|
|
|
|
String network = relation.getString("network");
|
|
|
|
String ref = relation.getString("ref");
|
|
|
|
|
|
|
|
if ("US:I".equals(network)) {
|
|
|
|
networkType = RouteNetwork.US_INTERSTATE;
|
|
|
|
} else if ("US:US".equals(network)) {
|
|
|
|
networkType = RouteNetwork.US_HIGHWAY;
|
|
|
|
} else if (network != null && network.length() == 5 && network.startsWith("US:")) {
|
|
|
|
networkType = RouteNetwork.US_STATE;
|
|
|
|
} else if (network != null && network.startsWith("CA:transcanada")) {
|
|
|
|
networkType = RouteNetwork.CA_TRANSCANADA;
|
|
|
|
}
|
|
|
|
|
|
|
|
int rank = switch (coalesce(network, "")) {
|
|
|
|
case "iwn", "nwn", "rwn" -> 1;
|
|
|
|
case "lwn" -> 2;
|
|
|
|
default -> (relation.hasTag("osmc:symbol") || relation.hasTag("colour")) ? 2 : 3;
|
|
|
|
};
|
|
|
|
|
2022-02-05 11:21:39 +00:00
|
|
|
if (network != null || rank < 3) {
|
2022-01-19 10:36:44 +00:00
|
|
|
return List.of(new RouteRelation(coalesce(ref, ""), network, networkType, (byte) rank, relation.id()));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
List<RouteRelation> getRouteRelations(Tables.OsmHighwayLinestring element) {
|
|
|
|
String ref = element.ref();
|
|
|
|
List<OsmReader.RelationMember<RouteRelation>> relations = element.source().relationInfo(RouteRelation.class);
|
|
|
|
List<RouteRelation> result = new ArrayList<>(relations.size() + 1);
|
|
|
|
for (var relationMember : relations) {
|
|
|
|
var relation = relationMember.relation();
|
|
|
|
// avoid duplicates - list should be very small and usually only one
|
|
|
|
if (!result.contains(relation)) {
|
|
|
|
result.add(relation);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (ref != null) {
|
|
|
|
// GB doesn't use regular relations like everywhere else, so if we are
|
|
|
|
// in GB then use a naming convention instead.
|
|
|
|
Matcher refMatcher = GREAT_BRITAIN_REF_NETWORK_PATTERN.matcher(ref);
|
|
|
|
if (refMatcher.find()) {
|
|
|
|
if (greatBritain == null) {
|
|
|
|
if (!loggedNoGb.get() && loggedNoGb.compareAndSet(false, true)) {
|
|
|
|
LOGGER.warn("No GB polygon for inferring route network types");
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
try {
|
|
|
|
Geometry wayGeometry = element.source().worldGeometry();
|
|
|
|
if (greatBritain.intersects(wayGeometry)) {
|
|
|
|
Transportation.RouteNetwork networkType =
|
2022-03-09 02:08:03 +00:00
|
|
|
"motorway".equals(element.highway()) ? Transportation.RouteNetwork.GB_MOTORWAY :
|
|
|
|
Transportation.RouteNetwork.GB_TRUNK;
|
2022-01-19 10:36:44 +00:00
|
|
|
String network = "motorway".equals(element.highway()) ? "omt-gb-motorway" : "omt-gb-trunk";
|
|
|
|
result.add(new RouteRelation(refMatcher.group(), network, networkType, (byte) -1,
|
|
|
|
0));
|
|
|
|
}
|
|
|
|
} catch (GeometryException e) {
|
|
|
|
e.log(stats, "omt_transportation_name_gb_test",
|
|
|
|
"Unable to test highway against GB route network: " + element.source().id());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Collections.sort(result);
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
RouteRelation getRouteRelation(Tables.OsmHighwayLinestring element) {
|
|
|
|
List<RouteRelation> all = getRouteRelations(element);
|
|
|
|
return all.isEmpty() ? null : all.get(0);
|
|
|
|
}
|
|
|
|
|
2021-07-13 10:09:36 +00:00
|
|
|
@Override
|
|
|
|
public void process(Tables.OsmHighwayLinestring element, FeatureCollector features) {
|
|
|
|
if (element.isArea()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-01-19 10:36:44 +00:00
|
|
|
RouteRelation routeRelation = getRouteRelation(element);
|
|
|
|
RouteNetwork networkType = routeRelation != null ? routeRelation.networkType : null;
|
|
|
|
|
2021-07-13 10:09:36 +00:00
|
|
|
String highway = element.highway();
|
|
|
|
String highwayClass = highwayClass(element.highway(), element.publicTransport(), element.construction(),
|
|
|
|
element.manMade());
|
2022-01-19 10:36:44 +00:00
|
|
|
String service = service(element.service());
|
2021-07-13 10:09:36 +00:00
|
|
|
if (highwayClass != null) {
|
2022-01-19 10:36:44 +00:00
|
|
|
if (isPierPolygon(element)) {
|
|
|
|
return;
|
2021-07-13 10:09:36 +00:00
|
|
|
}
|
2022-01-19 10:36:44 +00:00
|
|
|
int minzoom = getMinzoom(element, highwayClass);
|
2021-07-13 10:09:36 +00:00
|
|
|
|
2022-01-19 10:36:44 +00:00
|
|
|
boolean highwayRamp = isLink(highway);
|
|
|
|
Integer rampAboveZ12 = (highwayRamp || element.isRamp()) ? 1 : null;
|
|
|
|
Integer rampBelowZ12 = highwayRamp ? 1 : null;
|
2021-07-13 10:09:36 +00:00
|
|
|
|
|
|
|
FeatureCollector.Feature feature = features.line(LAYER_NAME).setBufferPixels(BUFFER_SIZE)
|
|
|
|
// main attributes at all zoom levels (used for grouping <= z8)
|
|
|
|
.setAttr(Fields.CLASS, highwayClass)
|
|
|
|
.setAttr(Fields.SUBCLASS, highwaySubclass(highwayClass, element.publicTransport(), highway))
|
|
|
|
.setAttr(Fields.BRUNNEL, brunnel(element.isBridge(), element.isTunnel(), element.isFord()))
|
2022-01-19 10:36:44 +00:00
|
|
|
.setAttr(Fields.NETWORK, networkType != null ? networkType.name : null)
|
|
|
|
// z8+
|
|
|
|
.setAttrWithMinzoom(Fields.EXPRESSWAY, element.expressway() && !"motorway".equals(highway) ? 1 : null, 8)
|
|
|
|
// z9+
|
|
|
|
.setAttrWithMinzoom(Fields.LAYER, nullIfLong(element.layer(), 0), 9)
|
2021-07-13 10:09:36 +00:00
|
|
|
.setAttrWithMinzoom(Fields.BICYCLE, nullIfEmpty(element.bicycle()), 9)
|
|
|
|
.setAttrWithMinzoom(Fields.FOOT, nullIfEmpty(element.foot()), 9)
|
|
|
|
.setAttrWithMinzoom(Fields.HORSE, nullIfEmpty(element.horse()), 9)
|
|
|
|
.setAttrWithMinzoom(Fields.MTB_SCALE, nullIfEmpty(element.mtbScale()), 9)
|
2022-01-19 10:36:44 +00:00
|
|
|
.setAttrWithMinzoom(Fields.ACCESS, access(element.access()), 9)
|
|
|
|
.setAttrWithMinzoom(Fields.TOLL, element.toll() ? 1 : null, 9)
|
|
|
|
// sometimes z9+, sometimes z12+
|
|
|
|
.setAttr(Fields.RAMP, minzoom >= 12 ? rampAboveZ12 :
|
|
|
|
((ZoomFunction<Integer>) z -> z < 9 ? null : z >= 12 ? rampAboveZ12 : rampBelowZ12))
|
|
|
|
// z12+
|
|
|
|
.setAttrWithMinzoom(Fields.SERVICE, service, 12)
|
|
|
|
.setAttrWithMinzoom(Fields.ONEWAY, nullIfInt(element.isOneway(), 0), 12)
|
2021-07-13 10:09:36 +00:00
|
|
|
.setAttrWithMinzoom(Fields.SURFACE, surface(element.surface()), 12)
|
2021-09-16 09:15:43 +00:00
|
|
|
.setMinPixelSize(0) // merge during post-processing, then limit by size
|
2021-09-18 00:18:06 +00:00
|
|
|
.setSortKey(element.zOrder())
|
2021-09-16 09:15:43 +00:00
|
|
|
.setMinZoom(minzoom);
|
2021-07-13 10:09:36 +00:00
|
|
|
|
|
|
|
if (isFootwayOrSteps(highway)) {
|
|
|
|
feature
|
|
|
|
.setAttr(Fields.LEVEL, Parse.parseLongOrNull(element.source().getTag("level")))
|
|
|
|
.setAttr(Fields.INDOOR, element.indoor() ? 1 : null);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-19 10:36:44 +00:00
|
|
|
int getMinzoom(Tables.OsmHighwayLinestring element, String highwayClass) {
|
|
|
|
List<RouteRelation> routeRelations = getRouteRelations(element);
|
|
|
|
int routeRank = 3;
|
|
|
|
for (var rel : routeRelations) {
|
|
|
|
if (rel.intRank() < routeRank) {
|
|
|
|
routeRank = rel.intRank();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
String highway = element.highway();
|
|
|
|
|
|
|
|
int minzoom;
|
|
|
|
if ("pier".equals(element.manMade())) {
|
|
|
|
minzoom = 13;
|
|
|
|
} else if (isResidentialOrUnclassified(highway)) {
|
|
|
|
minzoom = 12;
|
|
|
|
} else {
|
|
|
|
String baseClass = highwayClass.replace("_construction", "");
|
|
|
|
minzoom = switch (baseClass) {
|
|
|
|
case FieldValues.CLASS_SERVICE -> isDrivewayOrParkingAisle(service(element.service())) ? 14 : 13;
|
|
|
|
case FieldValues.CLASS_TRACK, FieldValues.CLASS_PATH -> routeRank == 1 ? 12 :
|
|
|
|
(z13Paths || !nullOrEmpty(element.name()) || routeRank <= 2 || !nullOrEmpty(element.sacScale())) ? 13 : 14;
|
|
|
|
default -> MINZOOMS.get(baseClass);
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
if (isLink(highway)) {
|
|
|
|
minzoom = Math.max(minzoom, 9);
|
|
|
|
}
|
|
|
|
return minzoom;
|
|
|
|
}
|
|
|
|
|
|
|
|
private boolean isPierPolygon(Tables.OsmHighwayLinestring element) {
|
|
|
|
if ("pier".equals(element.manMade())) {
|
|
|
|
try {
|
2022-03-09 02:08:03 +00:00
|
|
|
if (element.source().worldGeometry()instanceof LineString lineString && lineString.isClosed()) {
|
2022-01-19 10:36:44 +00:00
|
|
|
// ignore this because it's a polygon
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
} catch (GeometryException e) {
|
|
|
|
e.log(stats, "omt_transportation_pier",
|
|
|
|
"Unable to decode pier geometry for " + element.source().id());
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2021-07-13 10:09:36 +00:00
|
|
|
@Override
|
|
|
|
public void process(Tables.OsmRailwayLinestring element, FeatureCollector features) {
|
|
|
|
String railway = element.railway();
|
|
|
|
String clazz = railwayClass(railway);
|
|
|
|
if (clazz != null) {
|
|
|
|
String service = nullIfEmpty(element.service());
|
|
|
|
int minzoom;
|
|
|
|
if (service != null) {
|
|
|
|
minzoom = 14;
|
|
|
|
} else if (FieldValues.SUBCLASS_RAIL.equals(railway)) {
|
|
|
|
minzoom = "main".equals(element.usage()) ? 8 : 10;
|
|
|
|
} else if (FieldValues.SUBCLASS_NARROW_GAUGE.equals(railway)) {
|
|
|
|
minzoom = 10;
|
|
|
|
} else if (FieldValues.SUBCLASS_LIGHT_RAIL.equals(railway)) {
|
|
|
|
minzoom = 11;
|
|
|
|
} else {
|
|
|
|
minzoom = 14;
|
|
|
|
}
|
|
|
|
features.line(LAYER_NAME).setBufferPixels(BUFFER_SIZE)
|
|
|
|
.setAttr(Fields.CLASS, clazz)
|
|
|
|
.setAttr(Fields.SUBCLASS, railway)
|
|
|
|
.setAttr(Fields.SERVICE, service(service))
|
2022-01-19 10:36:44 +00:00
|
|
|
.setAttr(Fields.ONEWAY, nullIfInt(element.isOneway(), 0))
|
|
|
|
.setAttr(Fields.RAMP, element.isRamp() ? 1L : null)
|
2021-07-13 10:09:36 +00:00
|
|
|
.setAttrWithMinzoom(Fields.BRUNNEL, brunnel(element.isBridge(), element.isTunnel(), element.isFord()), 10)
|
2022-01-19 10:36:44 +00:00
|
|
|
.setAttrWithMinzoom(Fields.LAYER, nullIfLong(element.layer(), 0), 9)
|
2021-09-18 00:18:06 +00:00
|
|
|
.setSortKey(element.zOrder())
|
2021-10-20 01:57:47 +00:00
|
|
|
.setMinPixelSize(0) // merge during post-processing, then limit by size
|
2021-09-16 09:15:43 +00:00
|
|
|
.setMinZoom(minzoom);
|
2021-07-13 10:09:36 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void process(Tables.OsmAerialwayLinestring element, FeatureCollector features) {
|
|
|
|
features.line(LAYER_NAME).setBufferPixels(BUFFER_SIZE)
|
|
|
|
.setAttr(Fields.CLASS, "aerialway")
|
|
|
|
.setAttr(Fields.SUBCLASS, element.aerialway())
|
|
|
|
.setAttr(Fields.SERVICE, service(element.service()))
|
2022-01-19 10:36:44 +00:00
|
|
|
.setAttr(Fields.ONEWAY, nullIfInt(element.isOneway(), 0))
|
|
|
|
.setAttr(Fields.RAMP, element.isRamp() ? 1L : null)
|
2021-07-13 10:09:36 +00:00
|
|
|
.setAttr(Fields.BRUNNEL, brunnel(element.isBridge(), element.isTunnel(), element.isFord()))
|
2022-01-19 10:36:44 +00:00
|
|
|
.setAttr(Fields.LAYER, nullIfLong(element.layer(), 0))
|
2021-09-18 00:18:06 +00:00
|
|
|
.setSortKey(element.zOrder())
|
2021-10-20 01:57:47 +00:00
|
|
|
.setMinPixelSize(0) // merge during post-processing, then limit by size
|
2021-09-16 09:15:43 +00:00
|
|
|
.setMinZoom(12);
|
2021-07-13 10:09:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void process(Tables.OsmShipwayLinestring element, FeatureCollector features) {
|
|
|
|
features.line(LAYER_NAME).setBufferPixels(BUFFER_SIZE)
|
|
|
|
.setAttr(Fields.CLASS, element.shipway()) // "ferry"
|
|
|
|
// no subclass
|
|
|
|
.setAttr(Fields.SERVICE, service(element.service()))
|
2022-01-19 10:36:44 +00:00
|
|
|
.setAttr(Fields.ONEWAY, nullIfInt(element.isOneway(), 0))
|
|
|
|
.setAttr(Fields.RAMP, element.isRamp() ? 1L : null)
|
2021-07-13 10:09:36 +00:00
|
|
|
.setAttr(Fields.BRUNNEL, brunnel(element.isBridge(), element.isTunnel(), element.isFord()))
|
2022-01-19 10:36:44 +00:00
|
|
|
.setAttr(Fields.LAYER, nullIfLong(element.layer(), 0))
|
2021-09-18 00:18:06 +00:00
|
|
|
.setSortKey(element.zOrder())
|
2021-10-20 01:57:47 +00:00
|
|
|
.setMinPixelSize(0) // merge during post-processing, then limit by size
|
2021-09-16 09:15:43 +00:00
|
|
|
.setMinZoom(11);
|
2021-07-13 10:09:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void process(Tables.OsmHighwayPolygon element, FeatureCollector features) {
|
|
|
|
String manMade = element.manMade();
|
|
|
|
if (isBridgeOrPier(manMade) ||
|
2022-01-27 11:14:37 +00:00
|
|
|
// only allow closed ways where area=yes, and multipolygons
|
|
|
|
// and ignore underground pedestrian areas
|
|
|
|
(!element.source().canBeLine() && element.layer() >= 0)) {
|
2021-07-13 10:09:36 +00:00
|
|
|
String highwayClass = highwayClass(element.highway(), element.publicTransport(), null, element.manMade());
|
|
|
|
if (highwayClass != null) {
|
|
|
|
features.polygon(LAYER_NAME).setBufferPixels(BUFFER_SIZE)
|
|
|
|
.setAttr(Fields.CLASS, highwayClass)
|
|
|
|
.setAttr(Fields.SUBCLASS, highwaySubclass(highwayClass, element.publicTransport(), element.highway()))
|
|
|
|
.setAttr(Fields.BRUNNEL, brunnel("bridge".equals(manMade), false, false))
|
2022-01-19 10:36:44 +00:00
|
|
|
.setAttr(Fields.LAYER, nullIfLong(element.layer(), 0))
|
2021-09-18 00:18:06 +00:00
|
|
|
.setSortKey(element.zOrder())
|
2021-09-16 09:15:43 +00:00
|
|
|
.setMinZoom(13);
|
2021-07-13 10:09:36 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-09-16 09:15:43 +00:00
|
|
|
|
|
|
|
@Override
|
|
|
|
public List<VectorTile.Feature> postProcess(int zoom, List<VectorTile.Feature> items) {
|
|
|
|
double tolerance = config.tolerance(zoom);
|
2022-01-19 10:36:44 +00:00
|
|
|
double minLength = coalesce(MIN_LENGTH.apply(zoom), 0).doubleValue();
|
|
|
|
// TODO preserve direction for one-way?
|
2021-09-16 09:15:43 +00:00
|
|
|
return FeatureMerge.mergeLineStrings(items, minLength, tolerance, BUFFER_SIZE);
|
|
|
|
}
|
2022-01-19 10:36:44 +00:00
|
|
|
|
|
|
|
/** Information extracted from route relations to use when processing ways in that relation. */
|
|
|
|
record RouteRelation(
|
|
|
|
String ref,
|
|
|
|
String network,
|
|
|
|
RouteNetwork networkType,
|
|
|
|
byte rank,
|
|
|
|
@Override long id
|
|
|
|
) implements OsmRelationInfo, Comparable<RouteRelation> {
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public long estimateMemoryUsageBytes() {
|
|
|
|
return CLASS_HEADER_BYTES +
|
|
|
|
MemoryEstimator.estimateSize(rank) +
|
|
|
|
POINTER_BYTES + estimateSize(ref) +
|
|
|
|
POINTER_BYTES + estimateSize(network) +
|
|
|
|
POINTER_BYTES + // networkType
|
|
|
|
MemoryEstimator.estimateSizeLong(id);
|
|
|
|
}
|
|
|
|
|
|
|
|
public int intRank() {
|
|
|
|
return rank;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public int compareTo(RouteRelation o) {
|
|
|
|
return RELATION_ORDERING.compare(this, o);
|
|
|
|
}
|
|
|
|
}
|
2021-06-14 11:04:03 +00:00
|
|
|
}
|