Contents Prev: Applying geometric operations to polygons Next: Rendering a map

Visualizing data: restaurant density

JTS offer more powerful spatial operations. For instance it is pretty easy to check wheter a point lies within a polygon because the Geometry class provides the contains() method for this.

In this example, we're using this to visualize the density of restaurants for the boroughs of Berlin. First, we will load the geometries of all boroughs into memory and assemble their polygonal boundary. Then we will download all restaurant nodes in the area covered by the boroughs from Overpass and calculate the number of restaurants in each borough. After that we will determine a nice color depending on the density and print the results in GeoJSON.

See the results online, here's a preview:

Here's the code:

  public static void main(String[] args)
      throws IOException, EntityNotFoundException
  {
    GeometryBuilder geometryBuilder = new GeometryBuilder();
    RegionBuilder regionBuilder = new RegionBuilder();
 
    // This is where we get the boroughs from
    String urlBoroughs = "http://osmtestdata.topobyte.de/Berlin.admin10.all.tbo";
 
    // This is where we will get the restaurants from
    String queryTemplate = "http://overpass-api.de/api/interpreter?data="
        + "node[\"amenity\"=\"restaurant\"](%f,%f,%f,%f);out;";
 
    // Read boroughs
    InputStream input = new URL(urlBoroughs).openStream();
    OsmIterator iterator = new TboIterator(input, truefalse);
    InMemoryMapDataSet boroughsData = MapDataSetLoader.read(iterator,
        falsefalsetrue);
    input.close();
 
    // Build borough polygons and map their names
    List<MultiPolygon> boroughs = new ArrayList<>();
    Map<MultiPolygon, String> names = new HashMap<>();
 
    for (OsmRelation relation : boroughsData.getRelations()
        .valueCollection()) {
      Map<String, String> tags = OsmModelUtil.getTagsAsMap(relation);
      String name = tags.get("name");
      if (name == null) {
        continue;
      }
      try {
        RegionBuilderResult region = regionBuilder.build(relation,
            boroughsData);
        MultiPolygon polygon = region.getMultiPolygon();
        if (polygon.isEmpty()) {
          continue;
        }
        boroughs.add(polygon);
        names.put(polygon, name);
      } catch (EntityNotFoundException e) {
        System.out.println("Unable to build polygon: " + tags);
      }
    }
 
    // Calculate the bounding box for the data query
    Envelope env = boroughs.iterator().next().getEnvelopeInternal();
    for (MultiPolygon borough : boroughs) {
      env.expandToInclude(borough.getEnvelopeInternal());
    }
    BBox bbox = new BBox(env);
 
    // Define a query to retrieve some data
    String query = String.format(queryTemplate, bbox.getLat2(),
        bbox.getLon1(), bbox.getLat1(), bbox.getLon2());
 
    // Setup a map for counting values for each borough
    Map<MultiPolygon, Integer> counters = new HashMap<>();
    for (MultiPolygon borough : boroughs) {
      counters.put(borough, 0);
    }
 
    // Analyze nodes
    input = new URL(query).openStream();
    iterator = new OsmXmlIterator(input, true);
    for (EntityContainer entity : iterator) {
      if (entity.getType() != EntityType.Node) {
        break;
      }
      OsmNode node = (OsmNode) entity.getEntity();
      Map<String, String> tags = OsmModelUtil.getTagsAsMap(node);
 
      // This is not exactly necessary since the input will contain
      // restaurant nodes only anyway. Doing it anyway, your data soure
      // may be a different one.
      String amenity = tags.get("amenity");
      if (amenity == null || !amenity.equals("restaurant")) {
        continue;
      }
 
      // Determine which borough a restaurant is in, and increment the
      // counter
      Point point = geometryBuilder.build(node);
      for (MultiPolygon borough : boroughs) {
        if (borough.contains(point)) {
          counters.put(borough, counters.get(borough) + 1);
        }
      }
    }
 
    // Build density information
    Map<MultiPolygon, Double> densities = new HashMap<>();
    Map<MultiPolygon, Double> values = new HashMap<>();
 
    // Calculate density by dividing the counter by the borough's area
    for (MultiPolygon borough : boroughs) {
      int count = counters.get(borough);
      double density = count / borough.getArea();
      densities.put(borough, density);
    }
 
    // Determine a relative density scale from 0..1 depending on the minimum
    // and maximum density of all boroughs.
    // Then scale the relative density logarithmically to make it more
    // expressive.
    double min = Collections.min(densities.values());
    double max = Collections.max(densities.values());
    for (MultiPolygon borough : boroughs) {
      double density = densities.get(borough);
      double relative = (density - min) / (max - min);
 
      int exponent = 2;
      int upscale = (int) Math.pow(10, exponent) - 1;
      double value = Math.log10((1 + relative * upscale)) / exponent;
      values.put(borough, value);
    }
 
    // Build feature collection for output, assign colors ranging from
    // orange to blue
    Feature[] features = new Feature[boroughs.size()];
 
    for (int i = 0; i < boroughs.size(); i++) {
      MultiPolygon borough = boroughs.get(i);
      double value = values.get(borough);
 
      Map<String, Object> properties = new HashMap<>();
      properties.put("name", names.get(borough));
      properties.put("restaurants", counters.get(borough));
      properties.put("fill", color(value));
      properties.put("fill-opacity""0.5");
      properties.put("stroke""#555555");
 
      GeoJSONWriter writer = new GeoJSONWriter();
      org.wololo.geojson.Geometry g = writer.write(borough);
      features[i] = new Feature(g, properties);
    }
 
    FeatureCollection featureCollection = new FeatureCollection(features);
 
    String json = featureCollection.toString();
 
    System.out.println(GeoJsonHelper.prettyPrintFeatureCollection(json));
  }
Contents Prev: Applying geometric operations to polygons Next: Rendering a map