← Back to team overview

dhis2-devs team mailing list archive

[Branch ~dhis2-devs-core/dhis2/trunk] Rev 11356: PNG maps, simplified model

 

Merge authors:
  Lars Helge Øverland (larshelge)
------------------------------------------------------------
revno: 11356 [merge]
committer: Lars Helge Øverland <larshelge@xxxxxxxxx>
branch nick: dhis2
timestamp: Sat 2013-07-06 22:19:30 +0200
message:
  PNG maps, simplified model
removed:
  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/GeoToolsMapObject.java
modified:
  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/InternalMap.java
  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/MapUtils.java
  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/GeoToolsMapTest.java


--
lp:dhis2
https://code.launchpad.net/~dhis2-devs-core/dhis2/trunk

Your team DHIS 2 developers is subscribed to branch lp:dhis2.
To unsubscribe from this branch go to https://code.launchpad.net/~dhis2-devs-core/dhis2/trunk/+edit-subscription
=== 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 <kjetand@xxxxxxxxxx>
- * @author Olai Solheim <olais@xxxxxxxxxx>
- */
-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<GeoToolsMapObject> mapObjects;
-
-    /**
-     * Creates an empty map.
-     */
-    public GeoToolsMap()
-    {
-        this.mapObjects = new LinkedList<GeoToolsMapObject>();
-    }
-
-    /**
-     * Creates a map with the given initial map layer.
-     * 
-     * @param layer the initial map layer
-     */
-    public GeoToolsMap( InternalMapLayer layer )
-    {
-        this.mapObjects = new LinkedList<GeoToolsMapObject>();
-        this.addMapLayer( layer );
-    }
-
-    /**
-     * Creates a map with the given initial map layers.
-     * 
-     * @param layers the list of initial map layers
-     */
-    public GeoToolsMap( List<InternalMapLayer> layers )
-    {
-        this.mapObjects = new LinkedList<GeoToolsMapObject>();
-        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<GeoToolsMapObject> mapObjects )
-    {
-        this.mapObjects.addAll( mapObjects );
-    }
-
-    // -------------------------------------------------------------------------
-    // InternalMap implementation
-    // -------------------------------------------------------------------------
-
-    public void addMapLayer( InternalMapLayer layer )
-    {
-        for ( InternalMapObject mapObject : layer.getAllMapObjects() )
-        {
-            addMapObject( (GeoToolsMapObject) mapObject );
-        }
-    }
-
-    public void addAllMapLayers( List<InternalMapLayer> 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 <olais@xxxxxxxxxx>
- */
-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 <kjetand@xxxxxxxxxx>
  * @author Olai Solheim <olais@xxxxxxxxxx>
  */
-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<InternalMapObject> mapObjects = new ArrayList<InternalMapObject>();
+
+    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<InternalMapLayer> 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<InternalMapObject> getMapObjects()
+    {
+        return mapObjects;
+    }
+
+    public void setMapObjects( List<InternalMapObject> mapObjects )
+    {
+        this.mapObjects = mapObjects;
+    }
+    
+    //TODO remove
+
+    public InternalMap( InternalMapLayer layer )
+    {
+        this.mapObjects = new LinkedList<InternalMapObject>();
+        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 <olais@xxxxxxxxxx>
  */
-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