package org.activityinfo.server.report.generator.map; /* * #%L * ActivityInfo Server * %% * Copyright (C) 2009 - 2013 UNICEF * %% * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program. If not, see * <http://www.gnu.org/licenses/gpl-3.0.html>. * #L% */ import org.activityinfo.model.type.geo.AiLatLng; import org.activityinfo.legacy.shared.command.DimensionType; import org.activityinfo.legacy.shared.model.SiteDTO; import org.activityinfo.legacy.shared.reports.content.*; import org.activityinfo.legacy.shared.reports.model.Dimension; import org.activityinfo.legacy.shared.reports.model.MapSymbol; import org.activityinfo.legacy.shared.reports.model.PointValue; import org.activityinfo.legacy.shared.reports.model.layers.BubbleMapLayer; import org.activityinfo.legacy.shared.reports.model.layers.ScalingType; import org.activityinfo.legacy.shared.reports.util.mapping.Extents; import org.activityinfo.server.database.hibernate.entity.Indicator; import org.activityinfo.server.report.generator.map.cluster.Cluster; import org.activityinfo.server.report.generator.map.cluster.Clusterer; import org.activityinfo.server.report.generator.map.cluster.ClustererFactory; import java.util.*; public class BubbleLayerGenerator extends PointLayerGenerator<BubbleMapLayer> { public BubbleLayerGenerator(BubbleMapLayer layer, Map<Integer, Indicator> indicators) { super(layer, indicators); } @Override public Extents calculateExtents() { // PRE---PASS - calculate extents of sites WITH non-zero // values for this indicator Extents extents = Extents.emptyExtents(); for (SiteDTO site : sites) { if (hasValue(site, layer.getIndicatorIds())) { if (site.hasLatLong()) { extents.grow(site.getLatitude(), site.getLongitude()); } else { Extents siteExtents = getBounds(site); if (siteExtents != null) { extents.grow(siteExtents); } } } } return extents; } @Override public Margins calculateMargins() { return new Margins(layer.getMaxRadius()); } @Override public void generate(TiledMap map, MapContent content) { // define our symbol scaling RadiiCalculator radiiCalculator; if (layer.getScaling() == ScalingType.None || layer.getMinRadius() == layer.getMaxRadius()) { radiiCalculator = new FixedRadiiCalculator(layer.getMinRadius()); } else if (layer.getScaling() == ScalingType.Graduated) { radiiCalculator = new GsLogCalculator(layer.getMinRadius(), layer.getMaxRadius()); } else { radiiCalculator = new FixedRadiiCalculator(layer.getMinRadius()); } BubbleIntersectionCalculator intersectionCalculator = new BubbleIntersectionCalculator(layer.getMaxRadius()); Clusterer clusterer = ClustererFactory.fromClustering(layer.getClustering(), radiiCalculator, intersectionCalculator); // create the list of input point values List<PointValue> points = new ArrayList<PointValue>(); List<PointValue> unmapped = new ArrayList<PointValue>(); generatePoints(sites, map, layer, clusterer, points, unmapped); // Cluster points by the clustering algorithm set in the layer List<Cluster> clusters = clusterer.cluster(map, points); // add unmapped sites for (PointValue pv : unmapped) { content.getUnmappedSites().add(pv.getSite().getId()); } BubbleLayerLegend legend = new BubbleLayerLegend(); legend.setDefinition(layer); // create the markers List<BubbleMapMarker> markers = new ArrayList<BubbleMapMarker>(); for (Cluster cluster : clusters) { Point px = cluster.getPoint(); AiLatLng latlng = map.fromPixelToLatLng(px); BubbleMapMarker marker = new BubbleMapMarker(); for (PointValue pv : cluster.getPointValues()) { marker.getSiteIds().add(pv.getSite().getId()); } marker.setX(px.getX()); marker.setY(px.getY()); marker.setValue(cluster.sumValues()); marker.setRadius((int) cluster.getRadius()); marker.setLat(latlng.getLat()); marker.setLng(latlng.getLng()); marker.setAlpha(layer.getAlpha()); marker.setTitle(formatTitle(cluster)); marker.setIndicatorIds(new HashSet<Integer>(layer.getIndicatorIds())); marker.setClusterAmount(cluster.getPointValues().size()); marker.setClustering(layer.getClustering()); marker.setColor(layer.getBubbleColor()); if (marker.getValue() < legend.getMinValue()) { legend.setMinValue(marker.getValue()); } if (marker.getValue() > legend.getMaxValue()) { legend.setMaxValue(marker.getValue()); } markers.add(marker); } // sort order by symbol radius descending // (this assures that smaller symbols are drawn on // top of larger ones) Collections.sort(markers, new Comparator<MapMarker>() { @Override public int compare(MapMarker o1, MapMarker o2) { if (o1.getSize() > o2.getSize()) { return -1; } else if (o1.getSize() < o2.getSize()) { return 1; } return 0; } }); // number markers if applicable if (layer.getLabelSequence() != null) { numberMarkers(markers); } content.addLegend(legend); content.getMarkers().addAll(markers); } private String formatTitle(Cluster cluster) { if (cluster.hasTitle()) { return cluster.getTitle() + " - " + cluster.sumValues(); } else { return Double.toString(cluster.sumValues()); } } public void generatePoints(List<SiteDTO> sites, TiledMap map, BubbleMapLayer layer, Clusterer clusterer, List<PointValue> mapped, List<PointValue> unmapped) { for (SiteDTO site : sites) { if (hasValue(site, layer.getIndicatorIds())) { Point px = null; AiLatLng geoPoint = getPoint(site); if (geoPoint != null) { px = map.fromLatLngToPixel(geoPoint); } Double value = getValue(site, layer.getIndicatorIds()); if (value != null && value != 0) { PointValue pv = new PointValue(site, createSymbol(site, layer.getColorDimensions()), value, px); if (geoPoint != null || clusterer.isMapped(site)) { mapped.add(pv); } else { unmapped.add(pv); } } } } } // this was too complicated. // we should be able to achieve the same result using filters on the labels // --> schedule to remove @Deprecated public MapSymbol createSymbol(SiteDTO site, List<Dimension> dimensions) { MapSymbol symbol = new MapSymbol(); for (Dimension dimension : dimensions) { if (dimension.getType() == DimensionType.Partner) { symbol.put(dimension, new EntityCategory(site.getPartnerId())); } } return symbol; } private void numberMarkers(List<BubbleMapMarker> markers) { // sort the markers, left-to right, top to bottom so the label // sequence is spatially consistent Collections.sort(markers, new MapMarker.LRTBComparator()); // add the labels for (BubbleMapMarker marker : markers) { marker.setLabel(layer.getLabelSequence().next()); } } }