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}