001/**
002 * Copyright (c) 2011, The University of Southampton and the individual contributors.
003 * All rights reserved.
004 *
005 * Redistribution and use in source and binary forms, with or without modification,
006 * are permitted provided that the following conditions are met:
007 *
008 *   *  Redistributions of source code must retain the above copyright notice,
009 *      this list of conditions and the following disclaimer.
010 *
011 *   *  Redistributions in binary form must reproduce the above copyright notice,
012 *      this list of conditions and the following disclaimer in the documentation
013 *      and/or other materials provided with the distribution.
014 *
015 *   *  Neither the name of the University of Southampton nor the names of its
016 *      contributors may be used to endorse or promote products derived from this
017 *      software without specific prior written permission.
018 *
019 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
020 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
021 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
022 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
023 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
024 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
025 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
026 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
027 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
028 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
029 */
030package org.openimaj.vis.world;
031
032import java.util.ArrayList;
033import java.util.Collection;
034import java.util.HashMap;
035import java.util.List;
036import java.util.Map;
037
038import javax.xml.parsers.DocumentBuilder;
039import javax.xml.parsers.DocumentBuilderFactory;
040
041import org.openimaj.math.geometry.point.Point2d;
042import org.openimaj.math.geometry.point.Point2dImpl;
043import org.openimaj.math.geometry.shape.Polygon;
044import org.openimaj.math.geometry.shape.Rectangle;
045import org.openimaj.math.geometry.shape.Shape;
046import org.w3c.dom.Document;
047import org.w3c.dom.Element;
048import org.w3c.dom.Node;
049import org.w3c.dom.NodeList;
050
051/**
052 *      A class that encapsulates all the countries in the world, their shapes and
053 *      their codes. Allows mapping a country code to a specific geometry.
054 *
055 *      @author Sina Samangooei (ss@ecs.soton.ac.uk)
056 *      @author David Dupplaw (dpd@ecs.soton.ac.uk)
057 */
058public class WorldPolygons
059{
060        /** The XML document that contains the data */
061        private static Document doc;
062
063        /**
064         *      Here we read in the country data from the XML document.
065         *      Happens only once at the instantiation of the class.
066         *      It doesn't instantiate the actual data because we may
067         *      want multiple instances of this class containing different
068         *      information.
069         */
070        static
071        {
072                // Create a document building to read in the XML
073                final DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
074
075                try
076                {
077                        // Read in the document from the countries XML file
078                        final DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
079                        WorldPolygons.doc = dBuilder.parse(
080                                WorldPolygons.class.getResourceAsStream( "countries_world.kml" ) );
081
082                }
083                catch( final Exception e )
084                {
085                        throw new RuntimeException( e );
086                }
087        }
088
089        /** The shapes of the countries keyed via the country code */
090        private final Map<String, WorldPlace> countryShapes;
091
092        private final Map<String, WorldPlace> countryCodeShapes;
093
094        private final Rectangle bounds;
095
096        /**
097         *      Default constructor
098         */
099        public WorldPolygons()
100        {
101                this.countryShapes = new HashMap<String, WorldPlace>();
102                this.countryCodeShapes = new HashMap<String, WorldPlace>();
103
104                // Parse through the XML document and create the items.
105                WorldPolygons.doc.getDocumentElement().normalize();
106                final NodeList places = WorldPolygons.doc.getElementsByTagName( "Placemark" );
107                float minx = Integer.MAX_VALUE, miny = Integer.MAX_VALUE,
108                                maxx = -Integer.MAX_VALUE, maxy = -Integer.MAX_VALUE;
109
110                // For each place...
111                for( int i = 0; i < places.getLength(); i++ )
112                {
113                        final Node placeNode = places.item( i );
114
115                        /**
116                         * [name: null] [description: null] [LookAt: null] [Style: null]
117                         * [MultiGeometry: null]
118                         */
119                        final String name = this.getNodeValue( placeNode, "name" );
120                        final String desc = this.getNodeValue( placeNode, "description" );
121                        final String countryCode = desc.split( ":" )[0].split( "=" )[1].trim().toLowerCase();
122                        final Node lookat = this.getFirstNode( placeNode, "LookAt" );
123                        final String latStr = this.getNodeValue( lookat, "latitude" );
124                        final String lonStr = this.getNodeValue( lookat, "longitude" );
125                        final Element multiGeom = (Element) this.getFirstNode( placeNode, "MultiGeometry" );
126                        final NodeList polygonNodes = multiGeom.getElementsByTagName( "Polygon" );
127                        final List<Shape> polygons = new ArrayList<Shape>();
128                        for( int j = 0; j < polygonNodes.getLength(); j++ )
129                        {
130                                final String[] coords = this.getNodeValue( polygonNodes.item( j ), "coordinates" ).split( " " );
131                                final List<Point2d> points = new ArrayList<Point2d>();
132                                for( final String coord : coords )
133                                {
134                                        final String[] xy = coord.split( "," );
135                                        final float fx = Float.parseFloat( xy[0] );
136                                        final float fy = Float.parseFloat( xy[1] );
137                                        minx = Math.min( minx, fx );
138                                        miny = Math.min( miny, fy );
139                                        maxx = Math.max( maxx, fx );
140                                        maxy = Math.max( maxy, fy );
141                                        points.add( new Point2dImpl( fx, fy ) );
142                                }
143                                polygons.add( new Polygon( points ) );
144                        }
145
146                        final WorldPlace place = new WorldPlace( name, countryCode, Float.parseFloat( latStr ), Float.parseFloat( lonStr ), polygons );
147                        this.countryShapes.put( name, place );
148                        this.countryCodeShapes.put( countryCode, place );
149                }
150                this.bounds = new Rectangle( minx, miny, maxx - minx, maxy - miny );
151        }
152
153        private String getNodeValue( final Node node, final String nodeName )
154        {
155                final Node firstNode = this.getFirstNode( node, nodeName );
156                return firstNode.getFirstChild().getNodeValue();
157        }
158
159        private Node getFirstNode( final Node node, final String nodeName )
160        {
161                return ((Element) node).getElementsByTagName( nodeName ).item( 0 );
162        }
163
164        /**
165         *      Get all the {@link WorldPlace}s.
166         *      @return A collection of places.
167         */
168        public Collection<WorldPlace> getShapes()
169        {
170                return this.countryShapes.values();
171        }
172
173        /**
174         *      Returns a {@link WorldPlace} given a country code.
175         *      @param countryCode the ISOA2 country code.
176         *      @return The {@link WorldPlace}
177         */
178        public WorldPlace byCountryCode( final String countryCode )
179        {
180                return this.countryCodeShapes.get( countryCode.toLowerCase() );
181        }
182
183        /**
184         *      Returns a {@link WorldPlace} given a country name.
185         *      @param country The country
186         *      @return The {@link WorldPlace}
187         */
188        public WorldPlace byCountry( final String country )
189        {
190                return this.countryShapes.get( country );
191        }
192
193        /**
194         *      Get the bounds of all the polygons
195         *      @return The bounds
196         */
197        public Rectangle getBounds()
198        {
199                return this.bounds;
200        }
201}