package org.opentripplanner.analyst; import java.io.IOException; import java.io.Serializable; import java.util.HashMap; import java.util.Map; import org.geojson.Feature; import org.opentripplanner.common.geometry.GeometryUtils; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.vividsolutions.jts.geom.Geometry; import com.vividsolutions.jts.geom.MultiPolygon; import com.vividsolutions.jts.geom.Point; import com.vividsolutions.jts.geom.Polygon; public class PointFeature implements Serializable { private static final long serialVersionUID = -613136927314702334L; private static final ObjectMapper deserializer = new ObjectMapper(); private String id; private Geometry geom; private Map<String,Integer> properties; private double lat; private double lon; public PointFeature(){ // blank constructor for deserialization this(null); } public PointFeature(String id){ this.id = id; this.geom = null; this.properties = new HashMap<String,Integer>(); } public PointFeature(String id, Geometry g, HashMap<String,Integer> ad) throws EmptyPolygonException, UnsupportedGeometryException{ this.id = id; this.setGeom(g); this.properties = ad; } public void addAttribute( String id, Integer val ){ this.properties.put(id, val); } public void setGeom(Geometry geom) throws EmptyPolygonException, UnsupportedGeometryException { if (geom instanceof MultiPolygon) { if (geom.isEmpty()) { throw new EmptyPolygonException(); } if (geom.getNumGeometries() > 1) { // LOG.warn("Multiple polygons in MultiPolygon, using only the first."); // TODO percolate this warning up somehow } this.geom = geom.getGeometryN(0); } else if( geom instanceof Point || geom instanceof Polygon){ this.geom = geom; } else { throw new UnsupportedGeometryException( "Non-point, non-polygon Geometry, not supported." ); } // cache a representative point Point point = geom.getCentroid(); this.lat = point.getY(); this.lon = point.getX(); } public Polygon getPolygon(){ if( geom instanceof Polygon ){ return (Polygon)geom; } else { return null; } } public Geometry getGeom() { return geom; } public Map<String,Integer> getProperties() { return properties; } public String getId() { return id; } @SuppressWarnings("unchecked") public static PointFeature fromJsonNode(JsonNode feature) throws EmptyPolygonException, UnsupportedGeometryException { Feature geoJsonFeature; try { geoJsonFeature = deserializer.readValue(feature.traverse(), Feature.class); } catch (IOException e) { throw new UnsupportedGeometryException(e.getMessage()); } PointFeature ret = new PointFeature(geoJsonFeature.getId()); ret.setGeom(GeometryUtils.convertGeoJsonToJtsGeometry(geoJsonFeature.getGeometry())); Object structured = geoJsonFeature.getProperty("structured"); if (structured == null || !(structured instanceof Map)) return null; // The code below assume the structured map to have integers only ret.setAttributes((Map<String, Integer>)(structured)); return ret; } private void setAttributes(Map<String,Integer> properties) { this.properties = properties; } public void setId(String id) { this.id = id; } public double getLat() { return this.lat; } public double getLon() { return this.lon; } public void setLat(double lat) { this.lat = lat; } public void setLon(double lon) { this.lon = lon; } public int getProperty(String id) { return this.properties.get(id); } /** * Compare to another object. * * We can't use identity equality, because point features may be serialized and deserialized * and thus the same PointFeature may exist in memory more than once. For example, PointFeatures * are compared inside the conveyal/otpa-cluster project to figure out which origins have * returned from the compute cluster. */ public boolean equals (Object o) { if (o instanceof PointFeature) { PointFeature f = (PointFeature) o; return f.lat == this.lat && f.lon == this.lon && (f.geom == this.geom || f.geom != null && f.geom.equals(this.geom)) && (f.id == this.id || f.id != null && f.id.equals(this.id)) && this.properties.equals(f.properties); } return false; } /** * Hash the relevant features of this PointFeature for efficient use in HashSets, etc. * PointFeatures are put in HashSets in the conveyal/otpa-cluster project. */ public int hashCode () { return (int) (this.lat * 1000) + (int) (this.lon * 1000) + (this.geom != null ? this.geom.hashCode() : 0) + (this.id != null ? this.id.hashCode() : 0) + this.properties.hashCode(); } }