package com.onthegomap.planetiler.reader.osm; import com.carrotsearch.hppc.LongArrayList; import com.onthegomap.planetiler.geo.GeoUtils; import com.onthegomap.planetiler.reader.WithTags; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; /** * An input element read from OpenStreetMap data. * * @see OSM element data model */ public interface OsmElement extends WithTags { /** OSM element ID */ long id(); int cost(); enum Type { NODE, WAY, RELATION } /** An un-handled element read from the .osm.pbf file (i.e. file header). */ record Other( @Override long id, @Override Map tags ) implements OsmElement { @Override public int cost() { return 1 + tags.size(); } } /** A point on the earth's surface. */ final class Node implements OsmElement { private static final long MISSING_LOCATION = Long.MIN_VALUE; private final long id; private final Map tags; private final double lat; private final double lon; // bailed out of a record to make encodedLocation lazy since it is fairly expensive to compute private long encodedLocation = MISSING_LOCATION; public Node( long id, Map tags, double lat, double lon ) { this.id = id; this.tags = tags; this.lat = lat; this.lon = lon; } public Node(long id, double lat, double lon) { this(id, new HashMap<>(), lat, lon); } @Override public long id() { return id; } @Override public Map tags() { return tags; } public double lat() { return lat; } public double lon() { return lon; } public long encodedLocation() { if (encodedLocation == MISSING_LOCATION) { encodedLocation = GeoUtils.encodeFlatLocation(lon, lat); } return encodedLocation; } @Override public int cost() { return 1 + tags.size(); } @Override public boolean equals(Object obj) { if (obj == this) { return true; } if (obj == null || obj.getClass() != this.getClass()) { return false; } var that = (Node) obj; return this.id == that.id && Objects.equals(this.tags, that.tags) && Double.doubleToLongBits(this.lat) == Double.doubleToLongBits(that.lat) && Double.doubleToLongBits(this.lon) == Double.doubleToLongBits(that.lon); } @Override public int hashCode() { return Objects.hash(id, tags, lat, lon); } @Override public String toString() { return "Node[" + "id=" + id + ", " + "tags=" + tags + ", " + "lat=" + lat + ", " + "lon=" + lon + ']'; } } /** An ordered list of 2-2,000 nodes that define a polyline. */ record Way( @Override long id, @Override Map tags, LongArrayList nodes ) implements OsmElement { public Way(long id) { this(id, new HashMap<>(), new LongArrayList(5)); } @Override public int cost() { return 1 + tags.size() + nodes.size(); } } /** An ordered list of nodes, ways, and other relations. */ record Relation( @Override long id, @Override Map tags, List members ) implements OsmElement { public Relation(long id) { this(id, new HashMap<>(), new ArrayList<>()); } public Relation { if (members == null) { members = Collections.emptyList(); } } @Override public int cost() { return 1 + tags.size() + members.size() * 3; } /** * A node, way, or relation contained in a relation with an optional "role" to clarify the purpose of each member. */ public record Member( Type type, long ref, String role ) {} } }