=== removed file 'dhis-2/dhis-services/dhis-service-mapgeneration/src/main/java/org/hisp/dhis/mapgeneration/GeoToolsMap.java' --- dhis-2/dhis-services/dhis-service-mapgeneration/src/main/java/org/hisp/dhis/mapgeneration/GeoToolsMap.java 2013-07-06 16:56:35 +0000 +++ dhis-2/dhis-services/dhis-service-mapgeneration/src/main/java/org/hisp/dhis/mapgeneration/GeoToolsMap.java 1970-01-01 00:00:00 +0000 @@ -1,298 +0,0 @@ -package org.hisp.dhis.mapgeneration; - -/* - * Copyright (c) 2004-2012, University of Oslo - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * * Neither the name of the HISP project nor the names of its contributors may - * be used to endorse or promote products derived from this software without - * specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR - * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON - * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -import java.awt.Color; -import java.awt.Graphics2D; -import java.awt.Rectangle; -import java.awt.RenderingHints; -import java.awt.image.BufferedImage; -import java.util.LinkedList; -import java.util.List; - -import org.geotools.data.DataUtilities; -import org.geotools.feature.DefaultFeatureCollection; -import org.geotools.feature.SchemaException; -import org.geotools.feature.simple.SimpleFeatureBuilder; -import org.geotools.geometry.jts.ReferencedEnvelope; -import org.geotools.map.FeatureLayer; -import org.geotools.map.Layer; -import org.geotools.map.MapContent; -import org.geotools.renderer.GTRenderer; -import org.geotools.renderer.lite.StreamingRenderer; -import org.geotools.styling.SLD; -import org.geotools.styling.Style; -import org.opengis.feature.simple.SimpleFeature; -import org.opengis.feature.simple.SimpleFeatureType; - -import com.vividsolutions.jts.geom.Geometry; -import com.vividsolutions.jts.geom.MultiPolygon; -import com.vividsolutions.jts.geom.Point; -import com.vividsolutions.jts.geom.Polygon; - -/** - * This class can be used to render map objects onto a map image. The projection - * is transformed automatically to "EPSG 3785". - * - * @author Kjetil Andresen - * @author Olai Solheim - */ -public class GeoToolsMap - extends InternalMap -{ - private static final String CIRCLE = "Circle"; - private static final String POINT = "Point"; - private static final String POLYGON = "Polygon"; - private static final String MULTI_POLYGON = "MultiPolygon"; - private static final String GEOMETRIES = "geometries"; - - // The flat list of map objects in this map. - private List mapObjects; - - /** - * Creates an empty map. - */ - public GeoToolsMap() - { - this.mapObjects = new LinkedList(); - } - - /** - * Creates a map with the given initial map layer. - * - * @param layer the initial map layer - */ - public GeoToolsMap( InternalMapLayer layer ) - { - this.mapObjects = new LinkedList(); - this.addMapLayer( layer ); - } - - /** - * Creates a map with the given initial map layers. - * - * @param layers the list of initial map layers - */ - public GeoToolsMap( List layers ) - { - this.mapObjects = new LinkedList(); - this.addAllMapLayers( layers ); - } - - /** - * Adds a map object to this map. - * - * @param mapObject the map object - */ - public void addMapObject( GeoToolsMapObject mapObject ) - { - this.mapObjects.add( mapObject ); - } - - /** - * Adds all map objects contained in the list. - * - * @param mapObjects the list of map objects - */ - public void addMapObjects( List mapObjects ) - { - this.mapObjects.addAll( mapObjects ); - } - - // ------------------------------------------------------------------------- - // InternalMap implementation - // ------------------------------------------------------------------------- - - public void addMapLayer( InternalMapLayer layer ) - { - for ( InternalMapObject mapObject : layer.getAllMapObjects() ) - { - addMapObject( (GeoToolsMapObject) mapObject ); - } - } - - public void addAllMapLayers( List layers ) - { - for ( InternalMapLayer layer : layers ) - { - for ( InternalMapObject mapObject : layer.getAllMapObjects() ) - { - addMapObject( (GeoToolsMapObject) mapObject ); - } - } - } - - public BufferedImage render() - { - return render( DEFAULT_MAP_WIDTH ); - } - - public BufferedImage render( int imageWidth ) - { - MapContent map = new MapContent(); - - // Convert map objects to features, and add them to the map - for ( GeoToolsMapObject mapObject : mapObjects ) - { - try - { - map.addLayer( createFeatureLayerFromMapObject( mapObject ) ); - } - catch ( SchemaException ex ) - { - throw new RuntimeException( "Could not add map object: " + mapObject.toString() + ": " + ex.getMessage() ); - } - } - - // Create a renderer for this map - GTRenderer renderer = new StreamingRenderer(); - renderer.setMapContent( map ); - - // Calculate image height - // TODO Might want to add a margin of say 25 pixels surrounding the map - ReferencedEnvelope mapBounds = map.getMaxBounds(); - double imageHeightFactor = mapBounds.getSpan( 1 ) / mapBounds.getSpan( 0 ); - Rectangle imageBounds = new Rectangle( 0, 0, imageWidth, (int) Math.ceil( imageWidth * imageHeightFactor ) ); - - // Create an image and get the graphics context from it - BufferedImage image = new BufferedImage( imageBounds.width, imageBounds.height, BufferedImage.TYPE_INT_ARGB ); - Graphics2D g = (Graphics2D) image.getGraphics(); - - // Draw a background if the background color is specified - // NOTE It will be transparent otherwise, which is desired - if ( backgroundColor != null ) - { - g.setColor( backgroundColor ); - g.fill( imageBounds ); - } - - // Enable anti-aliasing if specified - if ( isAntiAliasingEnabled ) - { - g.setRenderingHint( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON ); - } - else - { - g.setRenderingHint( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF ); - } - - // Render the map - renderer.paint( g, imageBounds, mapBounds ); - - map.dispose(); - - return image; - } - - // ------------------------------------------------------------------------- - // Internal - // ------------------------------------------------------------------------- - - /** - * Creates a feature layer based on a map object. - */ - private Layer createFeatureLayerFromMapObject( GeoToolsMapObject mapObject ) - throws SchemaException - { - SimpleFeatureType featureType = createFeatureType( mapObject.getGeometry() ); - SimpleFeatureBuilder featureBuilder = new SimpleFeatureBuilder( featureType ); - DefaultFeatureCollection featureCollection = new DefaultFeatureCollection(); - - Style style = null; - - featureBuilder.add( mapObject.getGeometry() ); - SimpleFeature feature = featureBuilder.buildFeature( null ); - - featureCollection.add( feature ); - - // Create style for this map object - if ( mapObject.getGeometry() instanceof Point ) - { - style = SLD.createPointStyle( CIRCLE, mapObject.getStrokeColor(), mapObject.getFillColor(), - mapObject.getFillOpacity(), mapObject.getRadius() ); - } - else if ( mapObject.getGeometry() instanceof Polygon || mapObject.getGeometry() instanceof MultiPolygon ) - { - style = SLD.createPolygonStyle( mapObject.getStrokeColor(), mapObject.getFillColor(), - mapObject.getFillOpacity() ); - } - else - { - style = SLD.createSimpleStyle( featureType ); - } - - return new FeatureLayer( featureCollection, style ); - } - - /** - * Creates a feature type for a GeoTools geometric primitive. - */ - private SimpleFeatureType createFeatureType( Geometry geom ) - throws SchemaException - { - String type = ""; - - if ( geom instanceof Point ) - { - type = POINT; - } - else if ( geom instanceof Polygon ) - { - type = POLYGON; - } - else if ( geom instanceof MultiPolygon ) - { - type = MULTI_POLYGON; - } - else - { - throw new IllegalArgumentException(); - } - - return DataUtilities.createType( GEOMETRIES, "geometry:" + type + ":srid=3785" ); - } - - /** - * Creates an image with text indicating an error. - */ - @SuppressWarnings( "unused" ) - private BufferedImage createErrorImage( String error ) - { - String str = "Error creating map image: " + error; - BufferedImage image = new BufferedImage( 500, 25, BufferedImage.TYPE_INT_RGB ); - Graphics2D g = image.createGraphics(); - - g.setColor( Color.WHITE ); - g.fill( new Rectangle( 500, 25 ) ); - - g.setColor( Color.RED ); - g.drawString( str, 1, 12 ); - - return image; - } -} === modified file 'dhis-2/dhis-services/dhis-service-mapgeneration/src/main/java/org/hisp/dhis/mapgeneration/GeoToolsMapGenerationService.java' --- dhis-2/dhis-services/dhis-service-mapgeneration/src/main/java/org/hisp/dhis/mapgeneration/GeoToolsMapGenerationService.java 2013-07-06 16:06:50 +0000 +++ dhis-2/dhis-services/dhis-service-mapgeneration/src/main/java/org/hisp/dhis/mapgeneration/GeoToolsMapGenerationService.java 2013-07-06 20:17:27 +0000 @@ -99,8 +99,8 @@ // Build internal representation of a map using GeoTools, then render it // to an image - GeoToolsMap gtMap = new GeoToolsMap( mapLayer ); - BufferedImage mapImage = gtMap.render( height ); + InternalMap map = new InternalMap( mapLayer ); + BufferedImage mapImage = MapUtils.render( map, height ); // Build the legend set, then render it to an image LegendSet legendSet = new LegendSet( mapLayer ); @@ -268,11 +268,11 @@ return mapValues; } - private GeoToolsMapObject buildSingleGeoToolsMapObjectForMapLayer( InternalMapLayer mapLayer, + private InternalMapObject buildSingleGeoToolsMapObjectForMapLayer( InternalMapLayer mapLayer, double mapValue, OrganisationUnit orgUnit ) { // Create and setup an internal map object - GeoToolsMapObject mapObject = new GeoToolsMapObject(); + InternalMapObject mapObject = new InternalMapObject(); mapObject.setName( orgUnit.getName() ); mapObject.setValue( mapValue ); mapObject.setFillOpacity( mapLayer.getOpacity() ); @@ -281,7 +281,7 @@ // Build and set the GeoTools-specific geometric primitive that outlines // the org unit on the map - mapObject.buildAndApplyGeometryForOrganisationUnit( orgUnit ); + mapObject.setGeometry( InternalMapObject.buildAndApplyGeometryForOrganisationUnit( orgUnit ) ); // Add the map object to the map layer mapLayer.addMapObject( mapObject ); === removed file 'dhis-2/dhis-services/dhis-service-mapgeneration/src/main/java/org/hisp/dhis/mapgeneration/GeoToolsMapObject.java' --- dhis-2/dhis-services/dhis-service-mapgeneration/src/main/java/org/hisp/dhis/mapgeneration/GeoToolsMapObject.java 2012-03-30 13:07:32 +0000 +++ dhis-2/dhis-services/dhis-service-mapgeneration/src/main/java/org/hisp/dhis/mapgeneration/GeoToolsMapObject.java 1970-01-01 00:00:00 +0000 @@ -1,170 +0,0 @@ -package org.hisp.dhis.mapgeneration; - -/* - * Copyright (c) 2004-2012, University of Oslo - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * * Neither the name of the HISP project nor the names of its contributors may - * be used to endorse or promote products derived from this software without - * specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR - * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON - * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -import org.hisp.dhis.organisationunit.OrganisationUnit; - -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.vividsolutions.jts.geom.Geometry; - -/** - * This is an extension of InternalMapObject that describes map objects specific - * to the GeoTools platform. - * - * It encapsulates all the members of InternalMapObject with the extension to - * support addition of a single GeoTools geometric primitive that can be given - * to the GeoTools renderer directly to render the map, in addition to using the - * members of its superclass InternalMapObject. - * - * @author Olai Solheim - */ -public class GeoToolsMapObject - extends InternalMapObject -{ - private Geometry geometry; - - /** - * Gets the geometry for this map object which is any of the GeoTools - * primitives. - * - * @return the GeoTools geometric primitive - */ - public Geometry getGeometry() - { - return this.geometry; - } - - /** - * Sets the geometry for this map object which is any of the GeoTools - * primitives. - * - * @param geometry the GeoTools geometric primitive - */ - public void setGeometry( Geometry geometry ) - { - this.geometry = geometry; - } - - /** - * Builds the GeoTools geometric primitive for a given organisation unit and - * sets it for this map object. - * - * Quick guide to how geometry is stored in DHIS: - * - * Geometry for org units is stored in the DB as [[[[0.32, -33.87], [23.99, - * -43.02], ...]]], and may be retrieved by calling the getCoordinates - * method of OrganisationUnit. - * - * The coordinates vary according to feature type, which can be found with a - * call to getFeatureType of OrganisationUnit. It varies between the - * following structures (names are omitted in the actual coordinates - * string): - * - * multipolygon = [ polygon0 = [ shell0 = [ point0 = [0.32, -33.87], point1 - * = [23.99, -43.02], point2 = [...]], hole0 = [...], hole1 = [...]], - * polygon1 = [...] polygon2 = [...]] polygon = [ shell0 = [ point0 = [0.32, - * -33.87], point1 = [23.99, -43.02]], hole0 = [...], hole1 = [...]] - * - * point = [0.32, -33.87] - * - * Multi-polygons are stored as an array of polygons. Polygons are stored as - * an array of linear-rings, where the first linear-ring is the shell, and - * remaining linear-rings are the holes in the polygon. Linear-rings are - * stored as an array of points, which in turn is stored as an array of - * (two) components as a floating point type. - * - * There are three types of geometry that may be stored in a DHIS org unit: - * point, polygon, and multi-polygon. This method supports all three. - * - * NOTE However, as of writing, there is a bug in DHIS OrganisationUnit - * where when getFeatureType reports type Polygon, getCoordinates really - * returns coordinates in the format of type MultiPolygon. - * - * @param orgUnit the organisation unit - */ - public void buildAndApplyGeometryForOrganisationUnit( OrganisationUnit orgUnit ) - { - // The final GeoTools primitive - Geometry primitive = null; - - // The DHIS coordinates as string - String coords = orgUnit.getCoordinates(); - - // The json root that is parsed from the coordinate string - JsonNode root = null; - - try - { - // Create a parser for the json and parse it into root - JsonParser parser = new ObjectMapper().getJsonFactory().createJsonParser( coords ); - root = parser.readValueAsTree(); - } - catch ( Exception ex ) - { - throw new RuntimeException( ex ); - } - - // Use the factory to build the correct type based on the feature type - // Polygon is treated similarly as MultiPolygon - if ( OrganisationUnit.FEATURETYPE_POINT.equals( orgUnit.getFeatureType() ) ) - { - primitive = GeoToolsPrimitiveFromJsonFactory.createPointFromJson( root ); - } - else if ( OrganisationUnit.FEATURETYPE_POLYGON.equals( orgUnit.getFeatureType() ) ) - { - primitive = GeoToolsPrimitiveFromJsonFactory.createMultiPolygonFromJson( root ); - } - else if ( OrganisationUnit.FEATURETYPE_MULTIPOLYGON.equals( orgUnit.getFeatureType() ) ) - { - primitive = GeoToolsPrimitiveFromJsonFactory.createMultiPolygonFromJson( root ); - } - else - { - throw new RuntimeException( "Not sure what to do with the feature type '" + orgUnit.getFeatureType() + "'" ); - } - - // Set the geometry for this map object - this.geometry = primitive; - } - - /** - * Returns a string representing this object, e.g. "GeoToolsMapObject { - * name: "Khambia", value: 34.22, radius: 1.00, fillColor: - * java.awt.Color(255, 255, 255), fillOpacity: 0.75, strokeColor: - * java.awt.Color(0, 0, 0), strokeWidth: 2, geometry: MULTIPOLYGON(((5.2 - * 5.3)(8.2 9.5)(13.2 98.2))) }". - */ - public String toString() - { - return String.format( "GeoToolsMapObject {" + " name: \"%s\"," + " value: %.2f," + " radius: %d," - + " fillColor: %s," + " fillOpacity: %.2f" + " strokeColor: %s," + " strokeWidth: %d" + " geometry: %s" - + "}", name, value, radius, fillColor, fillOpacity, strokeColor, strokeWidth, geometry ); - } -} === modified file 'dhis-2/dhis-services/dhis-service-mapgeneration/src/main/java/org/hisp/dhis/mapgeneration/InternalMap.java' --- dhis-2/dhis-services/dhis-service-mapgeneration/src/main/java/org/hisp/dhis/mapgeneration/InternalMap.java 2011-12-13 09:43:27 +0000 +++ dhis-2/dhis-services/dhis-service-mapgeneration/src/main/java/org/hisp/dhis/mapgeneration/InternalMap.java 2013-07-06 20:17:27 +0000 @@ -28,7 +28,8 @@ */ import java.awt.Color; -import java.awt.image.BufferedImage; +import java.util.ArrayList; +import java.util.LinkedList; import java.util.List; /** @@ -43,87 +44,61 @@ * @author Kjetil Andresen * @author Olai Solheim */ -public abstract class InternalMap +public class InternalMap { - // The background color used by this map. protected Color backgroundColor = null; - // True if anti-aliasing is enabled, false otherwise. protected boolean isAntiAliasingEnabled = true; - - // The default map image width, in pixels. - protected static final int DEFAULT_MAP_WIDTH = 500; - - /** - * Gets the background color of this map. - * - * @return the background color, or null if not set - */ + + private List mapObjects = new ArrayList(); + + public InternalMap() + { + } + public Color getBackgroundColor() { - return this.backgroundColor; + return backgroundColor; } - /** - * Sets the background color of this map. - * - * Setting this to null enables a transparent background. - * - * @param backgroundColor the background color - */ public void setBackgroundColor( Color backgroundColor ) { this.backgroundColor = backgroundColor; } - /** - * Returns true if anti-aliasing is enabled for rendering, false otherwise. - * - * @return true if anti-aliasing is enabled, false otherwise - */ public boolean isAntiAliasingEnabled() { - return this.isAntiAliasingEnabled; - } - - /** - * Sets if anti-aliasing should be enabled for rendering. - * - * @param b true to enable anti-aliasing, false to disable - */ - public void setAntiAliasingEnabled( boolean b ) - { - this.isAntiAliasingEnabled = b; - } - - /** - * Adds a map layer to this map. - * - * @param layer the layer - */ - public abstract void addMapLayer( InternalMapLayer layer ); - - /** - * Adds all map layers contained in the list. - * - * @param layers the list of layers - */ - public abstract void addAllMapLayers( List layers ); - - /** - * Renders all map objects contained in this map to an image with the - * default image width. - * - * @return the java.awt.image.BufferedImage representing this map - */ - public abstract BufferedImage render(); - - /** - * Renders all map objects contained in this map to an image with the - * specified width. - * - * @param width the desired width of the map - * @return the java.awt.image.BufferedImage representing this map - */ - public abstract BufferedImage render( int imageWidth ); + return isAntiAliasingEnabled; + } + + public void setAntiAliasingEnabled( boolean isAntiAliasingEnabled ) + { + this.isAntiAliasingEnabled = isAntiAliasingEnabled; + } + + public List getMapObjects() + { + return mapObjects; + } + + public void setMapObjects( List mapObjects ) + { + this.mapObjects = mapObjects; + } + + //TODO remove + + public InternalMap( InternalMapLayer layer ) + { + this.mapObjects = new LinkedList(); + this.addMapLayer( layer ); + } + + public void addMapLayer( InternalMapLayer layer ) + { + for ( InternalMapObject mapObject : layer.getAllMapObjects() ) + { + this.mapObjects.add( mapObject ); + } + } } === modified file 'dhis-2/dhis-services/dhis-service-mapgeneration/src/main/java/org/hisp/dhis/mapgeneration/InternalMapObject.java' --- dhis-2/dhis-services/dhis-service-mapgeneration/src/main/java/org/hisp/dhis/mapgeneration/InternalMapObject.java 2011-12-13 09:43:27 +0000 +++ dhis-2/dhis-services/dhis-service-mapgeneration/src/main/java/org/hisp/dhis/mapgeneration/InternalMapObject.java 2013-07-06 20:19:05 +0000 @@ -29,6 +29,13 @@ import java.awt.Color; +import org.hisp.dhis.organisationunit.OrganisationUnit; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.vividsolutions.jts.geom.Geometry; + /** * An internal representation of a map object in a map layer. * @@ -44,7 +51,7 @@ * * @author Olai Solheim */ -public abstract class InternalMapObject +public class InternalMapObject { protected String name; @@ -63,189 +70,207 @@ protected InternalMapLayer mapLayer; protected Interval interval; + + private Geometry geometry; + + // ------------------------------------------------------------------------- + // Constructors + // ------------------------------------------------------------------------- + + public InternalMapObject() + { + } + + // ------------------------------------------------------------------------- + // Logic + // ------------------------------------------------------------------------- /** - * Gets the name of this map object. - * - * @return the name + * Builds the GeoTools geometric primitive for a given organisation unit and + * sets it for this map object. + * + * Quick guide to how geometry is stored in DHIS: + * + * Geometry for org units is stored in the DB as [[[[0.32, -33.87], [23.99, + * -43.02], ...]]], and may be retrieved by calling the getCoordinates + * method of OrganisationUnit. + * + * The coordinates vary according to feature type, which can be found with a + * call to getFeatureType of OrganisationUnit. It varies between the + * following structures (names are omitted in the actual coordinates + * string): + * + * multipolygon = [ polygon0 = [ shell0 = [ point0 = [0.32, -33.87], point1 + * = [23.99, -43.02], point2 = [...]], hole0 = [...], hole1 = [...]], + * polygon1 = [...] polygon2 = [...]] polygon = [ shell0 = [ point0 = [0.32, + * -33.87], point1 = [23.99, -43.02]], hole0 = [...], hole1 = [...]] + * + * point = [0.32, -33.87] + * + * Multi-polygons are stored as an array of polygons. Polygons are stored as + * an array of linear-rings, where the first linear-ring is the shell, and + * remaining linear-rings are the holes in the polygon. Linear-rings are + * stored as an array of points, which in turn is stored as an array of + * (two) components as a floating point type. + * + * There are three types of geometry that may be stored in a DHIS org unit: + * point, polygon, and multi-polygon. This method supports all three. + * + * NOTE However, as of writing, there is a bug in DHIS OrganisationUnit + * where when getFeatureType reports type Polygon, getCoordinates really + * returns coordinates in the format of type MultiPolygon. + * + * @param orgUnit the organisation unit */ + public static Geometry buildAndApplyGeometryForOrganisationUnit( OrganisationUnit orgUnit ) + { + // The final GeoTools primitive + Geometry primitive = null; + + // The DHIS coordinates as string + String coords = orgUnit.getCoordinates(); + + // The json root that is parsed from the coordinate string + JsonNode root = null; + + try + { + // Create a parser for the json and parse it into root + JsonParser parser = new ObjectMapper().getJsonFactory().createJsonParser( coords ); + root = parser.readValueAsTree(); + } + catch ( Exception ex ) + { + throw new RuntimeException( ex ); + } + + // Use the factory to build the correct type based on the feature type + // Polygon is treated similarly as MultiPolygon + if ( OrganisationUnit.FEATURETYPE_POINT.equals( orgUnit.getFeatureType() ) ) + { + primitive = GeoToolsPrimitiveFromJsonFactory.createPointFromJson( root ); + } + else if ( OrganisationUnit.FEATURETYPE_POLYGON.equals( orgUnit.getFeatureType() ) ) + { + primitive = GeoToolsPrimitiveFromJsonFactory.createMultiPolygonFromJson( root ); + } + else if ( OrganisationUnit.FEATURETYPE_MULTIPOLYGON.equals( orgUnit.getFeatureType() ) ) + { + primitive = GeoToolsPrimitiveFromJsonFactory.createMultiPolygonFromJson( root ); + } + else + { + throw new RuntimeException( "Not sure what to do with the feature type '" + orgUnit.getFeatureType() + "'" ); + } + + return primitive; + } + + // ------------------------------------------------------------------------- + // Getters and setters + // ------------------------------------------------------------------------- + public String getName() { return this.name; } - /** - * Sets the name of this map object. - * - * @param name the name - */ public void setName( String name ) { this.name = name; } - /** - * Gets the value for this map object. - * - * @return the value - */ public double getValue() { return this.value; } - /** - * Sets the value for this map object. - * - * @param value the value - */ public void setValue( double value ) { this.value = value; } - /** - * Gets the radius for this map object (if point). - * - * @return the radius - */ public int getRadius() { return this.radius; } - /** - * Sets the radius for this map object (if point). - * - * @param radius the fill color - */ public void setRadius( int radius ) { this.radius = radius; } - /** - * Gets the fill color for this map object. - * - * @return the fill color - */ public Color getFillColor() { return this.fillColor; } - /** - * Sets the fill color for this map object. - * - * @param fillColor the fill color - */ public void setFillColor( Color fillColor ) { this.fillColor = fillColor; } - /** - * Gets the fill opacity for this object. - * - * @return the fill opacity - */ public float getFillOpacity() { return this.fillOpacity; } - /** - * Sets the fill opacity for this object. - * - * @param fillOpacity the fill opacity - */ public void setFillOpacity( float fillOpacity ) { this.fillOpacity = fillOpacity; } - /** - * Gets the stroke color for this map object. - * - * @return the stroke color - */ public Color getStrokeColor() { return this.strokeColor; } - /** - * Sets the stroke color for this map object. - * - * @param strokeColor the stroke color - */ public void setStrokeColor( Color strokeColor ) { this.strokeColor = strokeColor; } - /** - * Gets the stroke width for this map object. - * - * @return the stroke width - */ public int getStrokeWidth() { return this.strokeWidth; } - /** - * Sets the stroke width for this map object. - * - * @param strokeWidth - */ public void setStrokeWidth( int strokeWidth ) { this.strokeWidth = strokeWidth; } - /** - * Gets the map layer this map object is associated with. - * - * @return the map layer - */ public InternalMapLayer getMapLayer() { return this.mapLayer; } - /** - * Sets the map layer this object is associated with. - * - * @param mapLayer the map layer - */ public void setMapLayer( InternalMapLayer mapLayer ) { this.mapLayer = mapLayer; } - /** - * Gets the interval this map object is associated with. - * - * @return the interval - */ public Interval getInterval() { return this.interval; } - /** - * Sets the interval this map object is associated with and updates this map - * object with the properties (e.g. fill color) from the given interval. - * - * @param interval the interval - */ public void setInterval( Interval interval ) { this.interval = interval; this.fillColor = interval.getColor(); } + public Geometry getGeometry() + { + return this.geometry; + } + + public void setGeometry( Geometry geometry ) + { + this.geometry = geometry; + } + /** * Returns a string representing this object, e.g. "InternalMapObject { * name: "Khambia", value: 34.22, radius: 1.00, fillColor: === modified file 'dhis-2/dhis-services/dhis-service-mapgeneration/src/main/java/org/hisp/dhis/mapgeneration/MapUtils.java' --- dhis-2/dhis-services/dhis-service-mapgeneration/src/main/java/org/hisp/dhis/mapgeneration/MapUtils.java 2012-03-30 13:07:32 +0000 +++ dhis-2/dhis-services/dhis-service-mapgeneration/src/main/java/org/hisp/dhis/mapgeneration/MapUtils.java 2013-07-06 20:17:27 +0000 @@ -28,8 +28,34 @@ */ import java.awt.Color; - +import java.awt.Graphics2D; +import java.awt.Rectangle; +import java.awt.RenderingHints; +import java.awt.image.BufferedImage; + +import org.geotools.data.DataUtilities; +import org.geotools.feature.DefaultFeatureCollection; +import org.geotools.feature.SchemaException; +import org.geotools.feature.simple.SimpleFeatureBuilder; +import org.geotools.geometry.jts.ReferencedEnvelope; +import org.geotools.map.FeatureLayer; +import org.geotools.map.Layer; +import org.geotools.map.MapContent; +import org.geotools.renderer.GTRenderer; +import org.geotools.renderer.lite.StreamingRenderer; +import org.geotools.styling.SLD; +import org.geotools.styling.Style; +import org.hisp.dhis.organisationunit.OrganisationUnit; +import org.opengis.feature.simple.SimpleFeature; +import org.opengis.feature.simple.SimpleFeatureType; + +import com.fasterxml.jackson.core.JsonParser; 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; /** * Utility class. @@ -40,7 +66,15 @@ { private static final String COLOR_PREFIX = "#"; private static final int COLOR_RADIX = 16; + + private static final String CIRCLE = "Circle"; + private static final String POINT = "Point"; + private static final String POLYGON = "Polygon"; + private static final String MULTI_POLYGON = "MultiPolygon"; + private static final String GEOMETRIES = "geometries"; + private static final int DEFAULT_MAP_WIDTH = 500; + /** * Linear interpolation of int. * @@ -131,4 +165,152 @@ { return json != null && json.size() > 0; } + + // ------------------------------------------------------------------------- + // Map + // ------------------------------------------------------------------------- + + public static BufferedImage render( InternalMap map ) + { + return render( map, DEFAULT_MAP_WIDTH ); + } + + public static BufferedImage render( InternalMap map, int imageWidth ) + { + MapContent mapContent = new MapContent(); + + // Convert map objects to features, and add them to the map + for ( InternalMapObject mapObject : map.getMapObjects() ) + { + try + { + mapContent.addLayer( createFeatureLayerFromMapObject( mapObject ) ); + } + catch ( SchemaException ex ) + { + throw new RuntimeException( "Could not add map object: " + mapObject.toString() + ": " + ex.getMessage() ); + } + } + + // Create a renderer for this map + GTRenderer renderer = new StreamingRenderer(); + renderer.setMapContent( mapContent ); + + // Calculate image height + // TODO Might want to add a margin of say 25 pixels surrounding the map + ReferencedEnvelope mapBounds = mapContent.getMaxBounds(); + double imageHeightFactor = mapBounds.getSpan( 1 ) / mapBounds.getSpan( 0 ); + Rectangle imageBounds = new Rectangle( 0, 0, imageWidth, (int) Math.ceil( imageWidth * imageHeightFactor ) ); + + // Create an image and get the graphics context from it + BufferedImage image = new BufferedImage( imageBounds.width, imageBounds.height, BufferedImage.TYPE_INT_ARGB ); + Graphics2D g = (Graphics2D) image.getGraphics(); + + // Draw a background if the background color is specified + // NOTE It will be transparent otherwise, which is desired + if ( map.getBackgroundColor() != null ) + { + g.setColor( map.getBackgroundColor() ); + g.fill( imageBounds ); + } + + // Enable anti-aliasing if specified + if ( map.isAntiAliasingEnabled() ) + { + g.setRenderingHint( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON ); + } + else + { + g.setRenderingHint( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF ); + } + + // Render the map + renderer.paint( g, imageBounds, mapBounds ); + + mapContent.dispose(); + + return image; + } + + /** + * Creates a feature layer based on a map object. + */ + public static Layer createFeatureLayerFromMapObject( InternalMapObject mapObject ) + throws SchemaException + { + SimpleFeatureType featureType = createFeatureType( mapObject.getGeometry() ); + SimpleFeatureBuilder featureBuilder = new SimpleFeatureBuilder( featureType ); + DefaultFeatureCollection featureCollection = new DefaultFeatureCollection(); + + Style style = null; + + featureBuilder.add( mapObject.getGeometry() ); + SimpleFeature feature = featureBuilder.buildFeature( null ); + + featureCollection.add( feature ); + + // Create style for this map object + if ( mapObject.getGeometry() instanceof Point ) + { + style = SLD.createPointStyle( CIRCLE, mapObject.getStrokeColor(), mapObject.getFillColor(), + mapObject.getFillOpacity(), mapObject.getRadius() ); + } + else if ( mapObject.getGeometry() instanceof Polygon || mapObject.getGeometry() instanceof MultiPolygon ) + { + style = SLD.createPolygonStyle( mapObject.getStrokeColor(), mapObject.getFillColor(), + mapObject.getFillOpacity() ); + } + else + { + style = SLD.createSimpleStyle( featureType ); + } + + return new FeatureLayer( featureCollection, style ); + } + + /** + * Creates a feature type for a GeoTools geometric primitive. + */ + public static SimpleFeatureType createFeatureType( Geometry geom ) + throws SchemaException + { + String type = ""; + + if ( geom instanceof Point ) + { + type = POINT; + } + else if ( geom instanceof Polygon ) + { + type = POLYGON; + } + else if ( geom instanceof MultiPolygon ) + { + type = MULTI_POLYGON; + } + else + { + throw new IllegalArgumentException(); + } + + return DataUtilities.createType( GEOMETRIES, "geometry:" + type + ":srid=3785" ); + } + + /** + * Creates an image with text indicating an error. + */ + public static BufferedImage createErrorImage( String error ) + { + String str = "Error creating map image: " + error; + BufferedImage image = new BufferedImage( 500, 25, BufferedImage.TYPE_INT_RGB ); + Graphics2D g = image.createGraphics(); + + g.setColor( Color.WHITE ); + g.fill( new Rectangle( 500, 25 ) ); + + g.setColor( Color.RED ); + g.drawString( str, 1, 12 ); + + return image; + } } === modified file 'dhis-2/dhis-services/dhis-service-mapgeneration/src/test/java/org/hisp/dhis/mapgenerator/GeoToolsMapObjectTest.java' --- dhis-2/dhis-services/dhis-service-mapgeneration/src/test/java/org/hisp/dhis/mapgenerator/GeoToolsMapObjectTest.java 2011-12-09 10:29:57 +0000 +++ dhis-2/dhis-services/dhis-service-mapgeneration/src/test/java/org/hisp/dhis/mapgenerator/GeoToolsMapObjectTest.java 2013-07-06 20:17:27 +0000 @@ -5,7 +5,7 @@ import java.awt.Color; import org.hisp.dhis.DhisSpringTest; -import org.hisp.dhis.mapgeneration.GeoToolsMapObject; +import org.hisp.dhis.mapgeneration.InternalMapObject; import org.junit.Ignore; import org.junit.Test; @@ -15,12 +15,12 @@ public class GeoToolsMapObjectTest extends DhisSpringTest { - private GeoToolsMapObject geoToolsMapObject; + private InternalMapObject geoToolsMapObject; @Override public void setUpTest() { - geoToolsMapObject = new GeoToolsMapObject(); + geoToolsMapObject = new InternalMapObject(); } @Test === modified file 'dhis-2/dhis-services/dhis-service-mapgeneration/src/test/java/org/hisp/dhis/mapgenerator/GeoToolsMapTest.java' --- dhis-2/dhis-services/dhis-service-mapgeneration/src/test/java/org/hisp/dhis/mapgenerator/GeoToolsMapTest.java 2012-11-20 17:04:08 +0000 +++ dhis-2/dhis-services/dhis-service-mapgeneration/src/test/java/org/hisp/dhis/mapgenerator/GeoToolsMapTest.java 2013-07-06 20:00:33 +0000 @@ -7,7 +7,7 @@ import java.awt.Color; import org.hisp.dhis.DhisSpringTest; -import org.hisp.dhis.mapgeneration.GeoToolsMap; +import org.hisp.dhis.mapgeneration.InternalMap; import org.junit.Ignore; import org.junit.Test; @@ -17,12 +17,12 @@ public class GeoToolsMapTest extends DhisSpringTest { - private GeoToolsMap geoToolsMap; + private InternalMap geoToolsMap; @Override public void setUpTest() { - geoToolsMap = new GeoToolsMap(); + geoToolsMap = new InternalMap(); } @Test