Skip to content

Commit 030eabb

Browse files
Merge pull request #7583 from ibi-group/osm-speed-up
Speed up OSM processing some more
2 parents aa90f80 + bb2dc02 commit 030eabb

9 files changed

Lines changed: 146 additions & 98 deletions

File tree

application/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmDatabase.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -772,8 +772,6 @@ private void processMultipolygonRelations() {
772772
innerWays.add(way);
773773
} else if (member.hasRoleOuter()) {
774774
outerWays.add(way);
775-
} else {
776-
LOG.warn("Unexpected role '{}' in multipolygon", member.getRole());
777775
}
778776
}
779777
processedAreas.add(relation);

application/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmModule.java

Lines changed: 23 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import com.google.common.collect.ImmutableMultimap;
1010
import com.google.common.collect.Multimap;
1111
import gnu.trove.iterator.TLongIterator;
12+
import gnu.trove.list.array.TDoubleArrayList;
1213
import java.util.ArrayList;
1314
import java.util.Collection;
1415
import java.util.HashMap;
@@ -372,7 +373,7 @@ private void buildBasicGraph(
372373
IntersectionVertex fromVertex = null;
373374
IntersectionVertex toVertex = null;
374375

375-
ArrayList<Coordinate> segmentCoordinates = new ArrayList<>();
376+
TDoubleArrayList segmentCoordinates = new TDoubleArrayList(100);
376377

377378
/*
378379
* Traverse through all the nodes of this edge. For nodes which are not shared with any other edge, do not create endpoints -- just
@@ -412,7 +413,8 @@ private void buildBasicGraph(
412413
* the only processing we do on other nodes is to accumulate their geometry
413414
*/
414415
if (segmentCoordinates.isEmpty()) {
415-
segmentCoordinates.add(osmStartNode.getCoordinate());
416+
segmentCoordinates.add(osmStartNode.lon);
417+
segmentCoordinates.add(osmStartNode.lat);
416418
}
417419

418420
if (
@@ -425,14 +427,14 @@ private void buildBasicGraph(
425427
osmEndNode.isEntrance() ||
426428
vertexGenerator.nodesInBarrierWays().containsKey(osmEndNode)
427429
) {
428-
segmentCoordinates.add(osmEndNode.getCoordinate());
430+
segmentCoordinates.add(osmEndNode.lon);
431+
segmentCoordinates.add(osmEndNode.lat);
429432

430-
geometry = GeometryUtils.getGeometryFactory().createLineString(
431-
segmentCoordinates.toArray(new Coordinate[0])
432-
);
433+
geometry = GeometryUtils.makeLineString(segmentCoordinates.toArray());
433434
segmentCoordinates.clear();
434435
} else {
435-
segmentCoordinates.add(osmEndNode.getCoordinate());
436+
segmentCoordinates.add(osmEndNode.lon);
437+
segmentCoordinates.add(osmEndNode.lat);
436438
continue;
437439
}
438440

@@ -554,28 +556,23 @@ private void buildBarrierEdges(VertexGenerator vertexGenerator) {
554556

555557
private Optional<Platform> getPlatform(OsmDatabase osmdb, OsmWay way) {
556558
var references = way.getMultiTagValues(params.boardingAreaRefTags());
557-
if (way.isBoardingLocation() && !references.isEmpty()) {
558-
var nodeRefs = way.getNodeRefs();
559-
var size = nodeRefs.size();
560-
var nodes = new Coordinate[size];
561-
for (int i = 0; i < size; i++) {
562-
nodes[i] = osmdb.getNode(nodeRefs.get(i)).getCoordinate();
563-
}
559+
if (!way.isBoardingLocation() || references.isEmpty()) {
560+
return Optional.empty();
561+
}
562+
var nodeRefs = way.getNodeRefs();
563+
var size = nodeRefs.size();
564+
var nodes = new Coordinate[size];
565+
for (int i = 0; i < size; i++) {
566+
nodes[i] = osmdb.getNode(nodeRefs.get(i)).getCoordinate();
567+
}
564568

565-
var geometryFactory = GeometryUtils.getGeometryFactory();
569+
var geometryFactory = GeometryUtils.getGeometryFactory();
566570

567-
var geometry = geometryFactory.createLineString(nodes);
571+
var geometry = geometryFactory.createLineString(nodes);
568572

569-
return Optional.of(
570-
new Platform(
571-
params.edgeNamer().getName(way, "platform " + way.getId()),
572-
geometry,
573-
references
574-
)
575-
);
576-
} else {
577-
return Optional.empty();
578-
}
573+
return Optional.of(
574+
new Platform(params.edgeNamer().getName(way, "platform " + way.getId()), geometry, references)
575+
);
579576
}
580577

581578
private void validateBarriers() {

application/src/main/java/org/opentripplanner/osm/model/OsmEntity.java

Lines changed: 84 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@
1010

1111
import java.time.Duration;
1212
import java.time.format.DateTimeParseException;
13-
import java.util.Arrays;
1413
import java.util.Collections;
1514
import java.util.HashMap;
15+
import java.util.HashSet;
1616
import java.util.Map;
1717
import java.util.Objects;
1818
import java.util.Optional;
@@ -21,7 +21,6 @@
2121
import java.util.function.Consumer;
2222
import java.util.regex.Matcher;
2323
import java.util.regex.Pattern;
24-
import java.util.stream.Collectors;
2524
import javax.annotation.Nullable;
2625
import org.opentripplanner.core.model.accessibility.Accessibility;
2726
import org.opentripplanner.core.model.i18n.I18NString;
@@ -37,6 +36,7 @@
3736
*/
3837
public abstract class OsmEntity {
3938

39+
private static final Pattern I18N_PATTERN = Pattern.compile("\\{(.*?)}");
4040
/**
4141
* highway=* values that we don't want to even consider when building the graph.
4242
*/
@@ -133,6 +133,24 @@ public abstract class OsmEntity {
133133
*/
134134
protected static final Set<String> CHECKED_MODES = Set.of("foot", "bicycle", "motorcar");
135135

136+
private static final Set<String> WALK_ONLY_HIGHWAY_VALUES = Set.of("footway", "step", "corridor");
137+
private static final Set<String> NO_ACCESS_VALUES = Set.of("no", "license", "dismount");
138+
private static final Set<String> HIGHWAY_BOARDING_LOCATION_VALUES = Set.of(
139+
"platform",
140+
"bus_stop"
141+
);
142+
private static final Set<String> RAILWAY_BOARDING_LOCATION_VALUES = Set.of(
143+
"tram_stop",
144+
"station",
145+
"halt"
146+
);
147+
private static final Set<String> AMENITY_BOARDING_LOCATION_VALUES = Set.of(
148+
"bus_station",
149+
"amenity",
150+
"ferry_terminal"
151+
);
152+
private static final Set<String> RAILWAY_PLATFORM_VALUES = Set.of("platform", "platform_edge");
153+
136154
/**
137155
* Mapping for the fallback key for checking access restrictions for each access mode in OSM
138156
* However, access is not included because we are skeptical of access=yes tags.
@@ -145,8 +163,6 @@ public abstract class OsmEntity {
145163
"bicycle",
146164
"vehicle"
147165
);
148-
private static final Set<String> WALK_ONLY_HIGHWAYS = Set.of("footway", "step", "corridor");
149-
private static final Set<String> NO_ACCESS_TAGS = Set.of("no", "license", "dismount");
150166
private static final Map<StreetTraversalPermission, String> OSM_TAGS_FOR_TRAVERSAL_PERMISSION =
151167
Map.of(
152168
StreetTraversalPermission.CAR,
@@ -157,6 +173,11 @@ public abstract class OsmEntity {
157173
"foot"
158174
);
159175

176+
/// This is nullable for performance reasons.
177+
///
178+
/// You could use an empty map, but using null allows you to skip the lower-casing and hash lookup
179+
/// per tag, which is the hottest path during OSM processing.
180+
@Nullable
160181
private final Map<String, String> tags;
161182

162183
protected final long id;
@@ -175,7 +196,7 @@ public abstract class OsmEntity {
175196
/**
176197
* Constructor for immutable OsmEntity
177198
*/
178-
protected OsmEntity(long id, Map<String, String> tags, OsmProvider osmProvider) {
199+
protected OsmEntity(long id, @Nullable Map<String, String> tags, OsmProvider osmProvider) {
179200
this.id = id;
180201
// calling Map.copyOf here costs about 10% of parsing performance, so we use
181202
// Collections.unmodifiableMap in the getter
@@ -202,20 +223,31 @@ public long getId() {
202223
* The tags of an entity (immutable).
203224
*/
204225
public Map<String, String> getTags() {
205-
return Collections.unmodifiableMap(tags);
226+
if (this.isTagless()) {
227+
return Map.of();
228+
} else {
229+
return Collections.unmodifiableMap(tags);
230+
}
206231
}
207232

208233
/**
209234
* Is the tag defined?
210235
*/
211-
public boolean hasTag(String tag) {
212-
return getTag(tag) != null;
236+
public final boolean hasTag(String tag) {
237+
return !this.isTagless() && getTag(tag) != null;
238+
}
239+
240+
/**
241+
* Does the entity contain any tags at all?
242+
*/
243+
final boolean isTagless() {
244+
return this.tags == null;
213245
}
214246

215247
/**
216248
* Determines if a tag contains a false value. 'no', 'false', and '0' are considered false.
217249
*/
218-
public boolean isTagFalse(String tag) {
250+
public final boolean isTagFalse(String tag) {
219251
return isFalse(getTag(tag));
220252
}
221253

@@ -235,17 +267,10 @@ public Accessibility explicitWheelchairAccessibility() {
235267
/**
236268
* Determines if a tag contains a true value. 'yes', 'true', and '1' are considered true.
237269
*/
238-
public boolean isTagTrue(String tag) {
270+
public final boolean isTagTrue(String tag) {
239271
return isTrue(getTag(tag));
240272
}
241273

242-
/**
243-
* Returns true if bicycle dismounts are forced.
244-
*/
245-
public boolean isBicycleDismountForced() {
246-
return isTag("bicycle", "dismount");
247-
}
248-
249274
public boolean isSidewalk() {
250275
return isTag("footway", "sidewalk") && isTag("highway", "footway");
251276
}
@@ -270,6 +295,9 @@ protected Optional<Permission> checkModePermission(String mode) {
270295
* or a parent mode, either with a directional suffix or not, empty if it is not specified.
271296
*/
272297
protected Optional<Permission> checkModePermission(String mode, TraverseDirection direction) {
298+
if (isTagless()) {
299+
return Optional.empty();
300+
}
273301
// check if the exact directional tag allows or denies access
274302
if (direction != DIRECTIONLESS) {
275303
if (isExplicitlyAllowed(mode + direction.tagSuffix())) {
@@ -309,7 +337,10 @@ protected boolean isExplicitlyAllowed(String key) {
309337
* Returns null if tag is not present.
310338
*/
311339
@Nullable
312-
public String getTag(String tag) {
340+
public final String getTag(String tag) {
341+
if (this.isTagless()) {
342+
return null;
343+
}
313344
tag = tag.toLowerCase();
314345
return tags.get(tag);
315346
}
@@ -470,8 +501,8 @@ public OptionalInt parseIntOrBoolean(String tag, Consumer<String> errorHandler)
470501
/**
471502
* Checks if a tag contains the specified value.
472503
*/
473-
public boolean isTag(String tag, String value) {
474-
return value != null && value.equals(tags.get(tag.toLowerCase()));
504+
public final boolean isTag(String tag, String value) {
505+
return !isTagless() && value != null && value.equals(tags.get(tag.toLowerCase()));
475506
}
476507

477508
/**
@@ -492,7 +523,7 @@ public boolean isOneOfTags(String key, Set<String> oneOfTags) {
492523
*/
493524
@Nullable
494525
public I18NString getAssumedName() {
495-
if (tags.containsKey("name")) {
526+
if (hasTag("name")) {
496527
return TranslatedString.getDeduplicatedI18NString(
497528
this.generateI18NForPattern("{name}"),
498529
false
@@ -506,8 +537,9 @@ public I18NString getAssumedName() {
506537
if (creativeName != null) {
507538
return this.creativeName;
508539
}
509-
if (tags.containsKey("ref")) {
510-
return new NonLocalizedString(tags.get("ref"));
540+
var ref = getTag("ref");
541+
if (ref != null) {
542+
return new NonLocalizedString(ref);
511543
}
512544
return null;
513545
}
@@ -524,7 +556,7 @@ public I18NString getAssumedName() {
524556
public Map<String, String> generateI18NForPattern(String pattern) {
525557
Map<String, StringBuffer> i18n = new HashMap<>();
526558
i18n.put(null, new StringBuffer());
527-
Matcher matcher = Pattern.compile("\\{(.*?)}").matcher(pattern);
559+
Matcher matcher = I18N_PATTERN.matcher(pattern);
528560

529561
int lastEnd = 0;
530562
while (matcher.find()) {
@@ -621,7 +653,7 @@ public Optional<TraverseDirection> isOneWay(@Nullable String mode) {
621653
return Optional.empty();
622654
}
623655

624-
if ("foot".equals(mode) && !isOneOfTags("highway", WALK_ONLY_HIGHWAYS)) {
656+
if ("foot".equals(mode) && !isOneOfTags("highway", WALK_ONLY_HIGHWAY_VALUES)) {
625657
return Optional.empty();
626658
}
627659

@@ -705,14 +737,14 @@ public boolean isParkAndRide() {
705737
* @return whether the node is a place used to board a public transport vehicle
706738
*/
707739
public boolean isBoardingLocation() {
740+
// the majority of nodes have no tags at all, so this yields a good speed-up
741+
if (isTagless()) {
742+
return false;
743+
}
708744
return (
709-
isTag("highway", "bus_stop") ||
710-
isTag("railway", "tram_stop") ||
711-
isTag("railway", "station") ||
712-
isTag("railway", "halt") ||
713-
isTag("amenity", "bus_station") ||
714-
isTag("amenity", "ferry_terminal") ||
715-
isTag("highway", "platform") ||
745+
isOneOfTags("highway", HIGHWAY_BOARDING_LOCATION_VALUES) ||
746+
isOneOfTags("railway", RAILWAY_BOARDING_LOCATION_VALUES) ||
747+
isOneOfTags("amenity", AMENITY_BOARDING_LOCATION_VALUES) ||
716748
isPlatform()
717749
);
718750
}
@@ -726,9 +758,7 @@ public boolean isBoardingLocation() {
726758
**/
727759
public boolean isPlatform() {
728760
var isPlatform =
729-
isTag("public_transport", "platform") ||
730-
isTag("railway", "platform") ||
731-
isTag("railway", "platform_edge");
761+
isTag("public_transport", "platform") || isOneOfTags("railway", RAILWAY_PLATFORM_VALUES);
732762
return isPlatform && !isTag("usage", "tourism");
733763
}
734764

@@ -775,14 +805,21 @@ public String url() {
775805
* Values are split by semicolons.
776806
*/
777807
public Set<String> getMultiTagValues(Set<String> refTags) {
778-
return refTags
779-
.stream()
780-
.map(this::getTag)
781-
.filter(Objects::nonNull)
782-
.flatMap(v -> Arrays.stream(v.split(";")))
783-
.map(String::strip)
784-
.filter(v -> !v.isBlank())
785-
.collect(Collectors.toUnmodifiableSet());
808+
// we try to keep the allocations low, so only allocate a small hash set by default
809+
Set<String> result = HashSet.newHashSet(2);
810+
for (var tag : refTags) {
811+
var value = getTag(tag);
812+
if (value == null) {
813+
continue;
814+
}
815+
for (var part : value.split(";")) {
816+
var stripped = part.strip();
817+
if (!stripped.isBlank()) {
818+
result.add(stripped);
819+
}
820+
}
821+
}
822+
return result;
786823
}
787824

788825
public OsmProvider getOsmProvider() {
@@ -842,10 +879,11 @@ public boolean isElevator() {
842879
* of other information.
843880
*/
844881
public boolean isWheelchairAccessible() {
845-
if (isTagTrue("wheelchair")) {
882+
var wheelchairValue = getTag("wheelchair");
883+
if (isTrue(wheelchairValue)) {
846884
return true;
847885
}
848-
if (isTagFalse("wheelchair")) {
886+
if (isFalse(wheelchairValue)) {
849887
return false;
850888
}
851889
if (isOneOfTags("barrier", WHEELCHAIR_INACCESSIBLE_BARRIERS)) {
@@ -891,7 +929,7 @@ public boolean isExplicitlyUnnamed() {
891929
* Returns true if this tag is explicitly access to this entity.
892930
*/
893931
private boolean isExplicitlyDenied(String key) {
894-
return isOneOfTags(key, NO_ACCESS_TAGS);
932+
return isOneOfTags(key, NO_ACCESS_VALUES);
895933
}
896934

897935
public StreetTraversalPermission getPermission() {

0 commit comments

Comments
 (0)