| package org.eclipse.stem.internal.data.utility; |
| |
| /******************************************************************************* |
| * Copyright (c) 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 |
| * IBM Corporation, BfR, and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v2.0 |
| * which accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation and new features |
| * Bundesinstitut für Risikobewertung - Pajek Graph interface, new Veterinary Models |
| *******************************************************************************/ |
| import java.awt.Polygon; |
| import java.awt.Rectangle; |
| import java.io.File; |
| import java.io.FileInputStream; |
| import java.io.FileNotFoundException; |
| import java.io.FileOutputStream; |
| import java.io.IOException; |
| import java.io.OutputStream; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.Date; |
| import java.util.Enumeration; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.ListIterator; |
| import java.util.Map; |
| import java.util.Properties; |
| import java.util.Set; |
| import java.util.StringTokenizer; |
| |
| import org.eclipse.emf.common.util.URI; |
| import org.eclipse.stem.definitions.adapters.spatial.geo.LatLong; |
| import org.eclipse.stem.definitions.adapters.spatial.geo.PlatformLatLongDataProvider; |
| import org.eclipse.stem.definitions.adapters.spatial.geo.LatLong.Segment; |
| |
| /** |
| * NeighborUtility generates the CommonBorder property file. |
| * |
| * <pre> |
| * NeighborUtility has to run as an Eclipse Plugin application |
| * because it uses Platform.LatLongProvider to process the |
| * LatLong xml files and it will only run in a plugin environment. |
| * |
| * *** NOTE *** |
| * The path for the Eclipse workspace is hardcoded below. |
| * Change the value for "baseFolder" to reflect your environment. |
| * Also the output is generated in whereever your system defines |
| * as the "Temporary" folder. You would then copy the files to |
| * org.eclipse.stem.internal.data |
| * resources/data/relationship/commonborder |
| * and commit them to CVS. |
| * |
| * To generate the required launch script: |
| * |
| * - From PackageExplorer, RightClick src for NeighborUtilityTest |
| * - Select RunAs->JUnit Plug-in Test |
| * It should startup a new image of Eclipse, run NeighborUtility |
| * and terminate. The console output should indicate any errors. |
| * |
| * |
| * To make it easier to invoke, we use the JUnit plugin environment |
| * to setup the plugin environment and invoke the following |
| * NeighborUtilityTest. |
| * NeighborUtilityTest will invoke the following methods in MeighborUtility |
| * <pre> |
| * doAllInternal - creates neighbor list for the admin 1 or 2 areas in |
| * each country. creates CCC_2_CCC_2.properties where |
| * CCC is the country code. |
| * |
| * doWorld - creates a list of neighboring countries from the |
| * list of all countries. creates ZZZ_0_ZZZ_0.properties |
| * |
| * doAllBorders - uses ZZZ_0_000.properties to identify neighboring |
| * countries and builds bordering neighbor lists |
| * from the admin 2 (or admin 1) areas in each country. |
| * |
| * |
| * The methods setBBox and ProcessBBox do the actual processing. |
| * |
| * |
| * API for setBBox and processBBox |
| * //Interate through the nodes. |
| * for (final Iterator nodeIter = canonicalGraph.getNodes().values() |
| * .iterator(); nodeIter.hasNext();) { |
| * final Node node = (Node) nodeIter.next(); |
| * final LatLongProvider latLongProvider = (LatLongProvider) LatLongProviderAdapterFactory.INSTANCE |
| * .adapt(node, LatLongProvider.class); |
| * //The node may or may not have lat/long data associated with |
| * //it, if it doesn't the list that's returned will be empty. |
| * final LatLong latLong = latLongProvider.getLatLong(); |
| * //Now for each node we create a BoundingBox for each polygon |
| * // and call setBBox to save the BoundingBox in a map. |
| * String name = node.getDublinCore().getTitle(); |
| * NeighborUtility.setBBox(map,bbox); |
| * } // for |
| * // We now have a map of AdminArea for one country and another map for |
| * // for the neighboring country |
| * //Now we call the processBBox method to do the processing |
| * // BBox1 is adjacent to BBox2 if any of the corners of BBox1 is contained |
| * // in BBox2 or any corner of BBox2 is contained in BBox1 |
| * // if that is true they they might be neighbors and we check further |
| * // We compare all of the points of Polygon1 to match against polygon2 |
| * // If we find a match then they are neighbors. |
| * // |
| * // If no match try again this time looking for points that match within |
| * // a small delta. |
| * |
| * // ifstill not match we make one more try, |
| * // check if any of the points for |
| * // polygon1 are contained inside polygon2 or visaversa. |
| * |
| * List<String[]> list = NeighborUtility.processBBox(bbox1,bbox2); |
| * -> testAdjacency(bbox1,bbox2) test AdminArea containment |
| * -> testAdjacencyMatch(bbox1,box2) Test if points match |
| * |
| * // The resulting ArrayList contains an array of 2 Strings , each |
| * // representing 2 neighboring areas. |
| * // We then call the writeProperties method to write the |
| * // properties file. |
| * writeProperties(list,admin0a,admin0b); |
| * // The properties files will be written to the temporary directory |
| * // On Windosw: c:/tmp/commonborders/CCC_2_XXX_2.properties |
| * // |
| * It needs to be copied to org.eclipse.stem.internal.data |
| * /resources.data/relationship/comonborder |
| * and then checked into CVS/SVN |
| * </pre> |
| * |
| */ |
| public class NeighborUtility { |
| |
| /** |
| * User 's unique folder where they keep all of the STEM code. ******* Must |
| * be changed ***** |
| */ |
| public static String workspaceFolder = "c:/stem/"; |
| |
| /** |
| * if true then debug info can be written |
| */ |
| public static boolean DEBUG = true; |
| |
| static int MAX = 50; |
| static int counter = 0; |
| |
| /** |
| * URI reference to the LatLong data baseURI/country/CCC/CCC_0_MAP.xml |
| */ |
| public static String baseURI = "platform:/plugin/org.eclipse.stem.geography/" |
| + "resources/data/geo/country/"; |
| /** |
| * Hard coded File reference to the above URI Needed because we don;t know |
| * how to convert a URI to a file path |
| */ |
| public static String baseFolder = workspaceFolder |
| + "org.eclipse.stem.geography/" + "resources/data/geo/country/"; |
| /** |
| * Hard coded File reference to the common border property files. Needed |
| * because we don;t know how to convert a URI to a file path |
| */ |
| public static String commonborderFolder = workspaceFolder |
| + "org.eclipse.stem.internal.data/" |
| + "resources/data/relationship/commonborder/"; |
| /** |
| * Used to build an error message for neighbors with no common border |
| */ |
| public static StringBuffer problems = new StringBuffer(2000); |
| |
| /** |
| * statistics |
| */ |
| private static int c_bboxNeighbors = 0; |
| private static int c_pointNeighbors = 0; |
| private static int c_closeNeighbors = 0; |
| private static int c_segmentNeighbors = 0; |
| private static int c_Neighbors = 0; |
| private static long c_start = new Date().getTime(); |
| private static final long start = new Date().getTime(); |
| |
| /** |
| * if true then only check for neighbors using BBox check This is used for |
| * generating the ZZZ world list. |
| */ |
| public static boolean bboxCheckOnly = false; |
| |
| // **************************************************************** |
| // static methods |
| // **************************************************************** |
| /** |
| * Build neighbor list for neighbors within a country We will call |
| * setBBoxArea for each Admin area in the country then we will call |
| * processBBox to prepare the list of neighbors. |
| * |
| * @param fixture |
| * instance of PlatformLatLongDataProvider |
| * @param admin0 |
| * Admin0 name for the country (i.e USA) |
| * @param adminFile |
| * Admin file (i.e. USA_2_MAP.xml) |
| * |
| * #return list of String[2] where [0] is an Admin Area (1 or 2) and [1] is |
| * a neighboring Admin area (1 or 2) |
| */ |
| static List<String[]> neighborCheck( |
| final PlatformLatLongDataProvider fixture, final String admin0, |
| final String adminFile) { |
| |
| final String uriStr = baseURI + admin0 + "/" + adminFile; |
| |
| // final URI uri = URI.createURI(uriString); |
| // final Collection<String> ids = fixture.getIds(uri); |
| /** |
| * Map of areas and Boundaryboxes used for the process. |
| */ |
| final Map<String, AdminArea[]> map = buildMap(fixture, uriStr); |
| // new HashMap<String,AdminArea[]>(4000); |
| // for (String id : ids) { |
| |
| // final LatLong latLong = fixture.getLatLong(URI.createURI(uriString |
| // + "#" + id)); |
| // NeighborUtility.setArea(map,id, latLong); |
| |
| // } // for each id |
| final List<String[]> list = NeighborUtility.processAreas(map); |
| |
| return list; |
| |
| } |
| |
| /** |
| * Build neighbor list for neighbors in bordering countries |
| * |
| * @param fixture |
| * @param admin0 |
| * Admin0A name for the country (i.e USA) |
| * @param adminFile |
| * AdminFileA file (i.e. USA_2_MAP.xml) |
| * @param admin0 |
| * Admin0B name for the country (i.e MEX) |
| * @param adminFile |
| * AdminFileB file (i.e. MEX_2_MAP.xml) |
| * @return List containing array of String |
| * |
| */ |
| static List<String[]> neighborCheck( |
| final PlatformLatLongDataProvider fixture, final String admin0A, |
| final String adminFileA, final String admin0B, |
| final String adminFileB) { |
| |
| final String uriString1 = baseURI + admin0A + "/" + adminFileA; |
| final String uriString2 = baseURI + admin0B + "/" + adminFileB; |
| final Map<String, AdminArea[]> map1 = buildMap(fixture, uriString1); |
| final Map<String, AdminArea[]> map2 = buildMap(fixture, uriString2); |
| |
| final List<String[]> list = NeighborUtility.matchArea(map1, map2); |
| |
| return list; |
| } // neighborCheck |
| |
| /** |
| * Build Map of areas and Boundaryboxes used for the process. |
| * |
| * @param fixture |
| * @param uriStr |
| * URI to obtain lat/long data |
| * |
| */ |
| |
| private static Map<String, AdminArea[]> buildMap( |
| final PlatformLatLongDataProvider fixture, final String uriStr) { |
| final Map<String, AdminArea[]> map = new HashMap<String, AdminArea[]>( |
| 4000); |
| final URI uri = URI.createURI(uriStr); |
| final Collection<String> ids = fixture.getIds(uri); |
| |
| for (final String id : ids) { |
| final LatLong latLong = fixture.getLatLong(URI.createURI(uriStr |
| + "#" + id)); |
| NeighborUtility.setArea(map, id, latLong); |
| |
| } // for each id |
| return map; |
| } |
| |
| /** |
| * Build neighbor list for neighbors within a country for all of the |
| * countries in the world. |
| * |
| * @param fixture |
| * |
| */ |
| static void doAllInternal(final PlatformLatLongDataProvider fixture) { |
| |
| final List<String> world = getAdmin0List(); |
| for (final String admin0 : world) { |
| |
| final String admin = getAdmin(admin0); |
| if (admin.contains("_0_")) { |
| // do not make xxx_0_xxx_0 file |
| continue; |
| } |
| if (admin != null) { |
| // debug(admin0+": "+admin); |
| output("** " + admin0); |
| final List<String[]> list = neighborCheck(fixture, admin0, |
| admin); |
| writeProperties(list, admin0, admin, admin0, admin); |
| statistics(); |
| if (list.size() == 0) { |
| problems.append(admin0 + ": " + admin + "\n"); |
| } |
| } |
| } |
| if (problems.length() > 0) { |
| debug("***"); |
| debug("*** Admin areas with no internal neighbors:"); |
| debug("***"); |
| debug(problems.toString()); |
| problems = new StringBuffer(2000); |
| } |
| |
| } |
| |
| /** |
| * This will generate a list of neighboring countries. |
| * ZZZ_0_ZZZ_0.properties |
| * |
| * @param fixture |
| * needed to get the LatLong data |
| * @param doAll |
| * true if doAllBorders will be run next using the returned list. |
| */ |
| static List<String[]> doWorld(final PlatformLatLongDataProvider fixture, |
| final boolean doAll) { |
| /** |
| * Map of areas and Boundaryboxes used for the process. |
| */ |
| final Map<String, AdminArea[]> map = new HashMap<String, AdminArea[]>( |
| 4000); |
| |
| final List<String> listZZZ = getAdmin0List(); |
| if (doAll) { |
| // we will get more world matches this way |
| // and remove them from the ZZZ list as we go |
| bboxCheckOnly = true; |
| |
| } |
| for (final String admin0 : listZZZ) { |
| // debug("Do: "+admin0); |
| if (admin0.equals("ALB") || admin0.equals("GRC")) { |
| debug(admin0); |
| } |
| |
| final String adminFile = getAdmin0(admin0); |
| if (!adminFile.contains("_0_")) { |
| output(admin0 + " admin 0 file does not exist"); |
| // continue; |
| } |
| final String uriString = baseURI + admin0 + "/" + adminFile; |
| final URI uri = URI.createURI(uriString); |
| final Collection<String> ids = fixture.getIds(uri); |
| if (ids == null || ids.size() == 0) { |
| output(admin0 + ": " + adminFile + " does not exist"); |
| } else { |
| for (final String id : ids) { |
| |
| final LatLong latLong = fixture.getLatLong(URI |
| .createURI(uriString + "#" + id)); |
| if (adminFile.contains("_0_")) { |
| // debug("call setArea for id: "+id); |
| NeighborUtility.setArea(map, id, latLong); |
| } else { |
| // Since we are using the admin1 file instead of |
| // the admin 9 file , we have to lie about the |
| // id for the polygons. |
| // debug("call setArea for admin0: "+admin0); |
| NeighborUtility.setArea(map, admin0, latLong); |
| } |
| } // for each id |
| } |
| |
| } |
| debug("doWorld: area setup complete."); |
| // find the neighboring countries based on bbox |
| final List<String[]> list = processAreas(map); |
| debug("ZZZ: "); |
| statistics(); |
| // list now has neighbors for each country in world |
| // Now process each set of bordering countries |
| |
| writeProperties(list, "ZZZ", "ZZZ_0", "ZZZ", "ZZZ_0"); |
| // statistics(); |
| return list; |
| } |
| |
| /** |
| * This will generate a list of neighbors for all neighboring countries. It |
| * uses an array list (from DoWorld) as the source of neighbor countries. |
| * |
| * @param fixture |
| * needed to get the LatLong data |
| * @param worldList |
| * List of neighboring admin0 areas |
| * @return true |
| */ |
| public static boolean doAllBorders( |
| final PlatformLatLongDataProvider fixture, |
| final List<String[]> worldList) { |
| output("doAllBorders: " + worldList.size()); |
| bboxCheckOnly = false; |
| final ListIterator<String[]> it = worldList.listIterator(); |
| while (it.hasNext()) { |
| final String[] b = it.next(); |
| final String admin0A = b[0]; |
| final String admin0B = b[1]; |
| final String adminA = getAdmin(admin0A); |
| final String adminB = getAdmin(admin0B); |
| if (adminA == null || adminB == null) { |
| continue; |
| } |
| output(admin0A + ":" + adminA + " : " + admin0B + ":" + adminB); |
| final List<String[]> list = neighborCheck(fixture, admin0A, adminA, |
| admin0B, adminB); |
| |
| if (list.size() == 0) { |
| output("*** Remove from ZZZ: " + admin0A + ":" + admin0B); |
| it.remove(); |
| } else { |
| writeProperties(list, admin0A, adminA, admin0B, adminB); |
| } |
| |
| statistics(); |
| } |
| writeProperties(worldList, "ZZZ", "ZZZ_0", "ZZZ", "ZZZ_0"); |
| return true; |
| } |
| |
| /** |
| * This will generate a list of neighbors for all neighboring countries. It |
| * uses ZZZ_0_ZZZ_0.properties as the source of neighbor countries. |
| * |
| * @param fixture |
| * needed to get the LatLong data |
| */ |
| static void doAllBorders(final PlatformLatLongDataProvider fixture) { |
| // get the version of ZZZ that we just built |
| // String folder = NeighborUtility.getPropertyFolder(); |
| // Get the checked in version of ZZZ |
| final String folder = commonborderFolder; |
| final List<String[]> worldList = new ArrayList<String[]>(); |
| Properties world = null; |
| File f = null; |
| FileInputStream is = null; |
| try { |
| f = new File(folder, "ZZZ_0_ZZZ_0.properties"); |
| is = new FileInputStream(f); |
| world = new Properties(); |
| world.load(is); |
| |
| String key = null; |
| final Enumeration<Object> e = world.keys(); |
| while (e.hasMoreElements()) { |
| key = (String) e.nextElement(); |
| try { |
| @SuppressWarnings("unused") |
| final int n = Integer.parseInt(key); |
| } catch (final NumberFormatException e1) { |
| continue; |
| } |
| |
| final String value = world.getProperty(key); |
| if (value != null) { |
| final StringTokenizer st = new StringTokenizer(value, ","); |
| if (st.countTokens() >= 2) { |
| final String v1 = st.nextToken().trim(); |
| final String v2 = st.nextToken().trim(); |
| |
| final String[] b = { v1, v2 }; |
| worldList.add(b); |
| } |
| } |
| } |
| output("worldList size: " + worldList.size()); |
| doAllBorders(fixture, worldList); |
| } catch (final FileNotFoundException e1) { |
| e1.printStackTrace(); |
| } catch (final IOException e1) { |
| e1.printStackTrace(); |
| } finally { |
| try { |
| if (is != null) { |
| is.close(); |
| } |
| } catch (Exception e) { |
| // Ignore exceptions on close |
| } |
| } |
| } |
| |
| /** |
| * Get the file name for the most detailed admin area for the specified |
| * admin 0 |
| * |
| * @param admin0 |
| * @return file name for the admin area (i.e. USA_2_MAP.xml) |
| */ |
| public static String getAdmin(final String admin0) { |
| String adminfile2 = null; |
| String adminfile0 = null; |
| final File parent = new File(baseFolder + "/" + admin0); |
| if (parent.exists()) { |
| final String[] files = parent.list(); |
| for (final String file : files) { |
| if (file.contains("_2_")) { |
| adminfile2 = file; |
| break; |
| } else if (file.contains("_1_")) { |
| // use _1_ if _2_ does not exist |
| adminfile2 = file; |
| } else if (file.contains("_0_")) { |
| adminfile0 = file; |
| } |
| } |
| } |
| if (adminfile2 == null) { |
| return adminfile0; |
| } |
| return adminfile2; |
| } |
| |
| /** |
| * Get the file name for the admin0 file (or admin1 if admin0 does not |
| * exist) |
| * |
| * @param admin0 |
| * @return file name for the admin area (i.e. USA_0_MAP.xml) |
| */ |
| public static String getAdmin0(final String admin0) { |
| String admin = null; |
| final File parent = new File(baseFolder + "/" + admin0); |
| if (parent.exists()) { |
| final String[] files = parent.list(); |
| for (final String file : files) { |
| if (file.contains("_0_")) { |
| admin = file; |
| break; |
| } else if (file.contains("_1_")) { |
| admin = file; |
| break; |
| } else if (file.contains("_2_")) { |
| admin = file; |
| debug("getAdmin0: file: " + file); |
| } |
| } |
| } |
| return admin; |
| } |
| |
| /** |
| * Get the list of admin 0 areas |
| * |
| * @return List of admin0 areas ( 3 character country codes) |
| */ |
| static List<String> getAdmin0List() { |
| final String worldFolder = baseFolder; |
| final String[] admin0 = new File(worldFolder).list(); |
| |
| final List<String> world = new ArrayList<String>(); |
| for (final String e : admin0) { |
| if (e.equals(".svn")) { |
| continue; |
| } |
| if (e.equals("CVS")) { |
| continue; |
| } |
| world.add(e); |
| } |
| return world; |
| } |
| |
| /** |
| * Write the property file based on the List of neighbors if called with |
| * (list,"USA","USA_2_MAP", "MEX","MEX_2_MAP" then it will write a file |
| * USA_2_MEX_2.properties that contains entries like 1 = US-AZ-35023, SON, 0 |
| * |
| * @param list |
| * List of String[2] |
| * @param admin0A |
| * Admin0 code for area 1 |
| * @param adminA |
| * Admin file |
| * @param admin0B |
| * Admin0 code for area 2 |
| * @param adminB |
| * Admin file |
| */ |
| static void writeProperties(final List<String[]> list, |
| final String admin0A, final String adminA, final String admin0B, |
| final String adminB) { |
| try { |
| OutputStream os = null; |
| try { |
| |
| final File folder = new File(NeighborUtility |
| .getPropertyFolder()); |
| if (!folder.mkdir()) { |
| throw new IOException("Unable to create property folder: "+ folder.getAbsolutePath()); |
| } |
| final File fn = new File(folder, adminA.substring(0, 5) + "_" |
| + adminB.substring(0, 5) + ".properties"); |
| debug("Output File: " + fn); |
| |
| os = new FileOutputStream(fn, false); |
| |
| String title = "Common Border relationship between country " |
| + admin0A + " and " + admin0B; |
| if (admin0A.equals(admin0B)) { |
| title = "Common Border relationship within country " |
| + admin0A; |
| } |
| if (admin0A.equals("ZZZ")) { |
| title = "Common Border relationship between all countries in the world"; |
| } |
| String comments = "# " + "\n" + "# File generated from:" |
| + "\n" + "# " + baseFolder + admin0A + "/" + adminA |
| + "\n" + "# " + baseFolder + admin0B + "/" + adminB |
| + "\n" + "#\n" + "# Generated by: " |
| + NeighborUtility.class + "\n" + "# " + "\n"; |
| if (admin0A.equals(admin0B)) { |
| comments = "# " + "\n" + "# File generated from:" + "\n" |
| + "# " + baseFolder + admin0A + "/" + adminA |
| + "\n" + "#\n" + "# Generated by: " |
| + NeighborUtility.class + "\n" + "# " + "\n"; |
| } |
| os.write(comments.getBytes()); |
| String property = "RECORD_CLASSNAME = org.eclipse.stem.internal.data.CommonBorderRelationshipGraphRecord" |
| + "\n"; |
| os.write(property.getBytes()); |
| property = "TITLE = " + title + "\n"; |
| os.write(property.getBytes()); |
| property = "VALID = start=2006-01-01;" + "\n"; |
| os.write(property.getBytes()); |
| final String levelA = adminA.substring(4, 5); |
| final String levelB = adminB.substring(4, 5); |
| String keyInfo = "ISOKEY_0 = " + admin0A + "\n"; |
| keyInfo += "ISOKEY_0_LEVEL = " + levelA + "\n"; |
| keyInfo += "ISOKEY_1 = " + admin0B + "\n"; |
| keyInfo += "ISOKEY_1_LEVEL = " + levelB + "\n"; |
| os.write(keyInfo.getBytes()); |
| final int n = list.size(); |
| for (int i = 0; i < n; i++) { |
| final String[] entry = list.get(i); |
| final StringBuffer sb = new StringBuffer(80); |
| sb.append("" + (i + 1)); |
| sb.append(" = "); |
| sb.append(entry[0]); |
| sb.append(", "); |
| sb.append(entry[1]); |
| sb.append(", 0"); |
| sb.append("\n"); |
| os.write(sb.toString().getBytes()); |
| } |
| |
| os.flush(); |
| os.close(); |
| } catch (final FileNotFoundException e) { |
| e.printStackTrace(); |
| } |
| } catch (final IOException e) { |
| e.printStackTrace(); |
| } |
| } |
| |
| /** |
| * Called to input another LatLong object This builds a Map of lat/long |
| * areas for an admin area |
| * |
| * @param map |
| * HashMap of ID to BoundaryBox for polygon |
| * @param id |
| * Admin area id |
| * @param latLong |
| * Latitude/longitude info |
| */ |
| public static void setArea(final Map<String, AdminArea[]> map, |
| final String id, final LatLong latLong) { |
| final List<Segment> list = latLong.getSegments(); |
| final int s = list.size(); |
| final Segment[] segments = new Segment[s]; |
| for (int i = 0; i < s; i++) { |
| segments[i] = list.get(i); |
| } |
| final Polygon[] polygons = getPolygons(latLong); |
| final int n = polygons.length; |
| final AdminArea[] areas = new AdminArea[n]; |
| for (int i = 0; i < n; i++) { |
| final Rectangle rectangle = polygons[i].getBounds(); |
| areas[i] = new AdminArea(segments[i], rectangle, id); |
| } |
| // debug("create: "+name+": "); |
| final AdminArea[] orig = map.get(id); |
| if (orig == null) { |
| map.put(id, areas); |
| } else { |
| final int l = areas.length; |
| int o = orig.length; |
| final AdminArea[] newAreas = new AdminArea[l + o]; |
| for (int i = 0; i < o; i++) { |
| newAreas[i] = orig[i]; |
| } |
| final int a = areas.length; |
| for (int i = 0; i < a; i++, o++) { |
| newAreas[o] = areas[i]; |
| } |
| map.put(id, newAreas); |
| } |
| // } |
| } |
| |
| /** |
| * |
| * After all the latLong data has been submitted this method is called to |
| * generate the list of internal neighbors from this Map of AdminAreas. |
| * |
| * BBox1 is adjacent to BBox2 if any of the corners of BBox1 is contained in |
| * BBox2 or any corner of BBox2 is contained in BBox1 |
| * |
| * @param map |
| * Map of AdminArea rectangles keyed by admin area |
| * |
| * @return List of neighbors. |
| */ |
| public static List<String[]> processAreas(final Map<String, AdminArea[]> map) { |
| /** |
| * List of neighboring areas. This is the result and needs to be turned |
| * into a properties file. |
| */ |
| final List<String[]> outlist = new ArrayList<String[]>(4000); |
| int counter = 0; |
| String[] entry; // = new String[2]; |
| |
| final Set<String> set = map.keySet(); |
| final int n = set.size(); |
| final String[] keys = set.toArray(new String[n]); |
| Arrays.sort(keys); |
| for (int i = 0; i < n; i++) { |
| final String key = keys[i]; |
| |
| // Rectangle[] bboxes = map.get(key); |
| // debug(key); |
| for (int j = i + 1; j < n; j++) { |
| if (key.contains("ALB") && keys[j].contains("GRC")) { |
| debug(key); |
| } |
| final boolean neighbor = isNeighbor(map, map, key, keys[j]); |
| if (neighbor) { |
| c_Neighbors++; |
| counter++; |
| debug(counter + " = " + key + ", " + keys[j] + ", 0"); |
| entry = new String[2]; |
| entry[0] = key; |
| entry[1] = keys[j]; |
| outlist.add(entry); |
| } |
| } |
| } |
| // debug("Number of Neighbors: "+counter); |
| return outlist; |
| } |
| |
| /** |
| * |
| * After all the latLong data has been submitted this method is called to |
| * generate the list of neighbors between the first set of Admin areas and |
| * the 2nd |
| * |
| * |
| * |
| * @param map1 |
| * Map of AdminArea rectangles keyed by admin area |
| * @param map2 |
| * Map of AdminArea rectangles keyed by admin area |
| * @return List of neighbors. |
| */ |
| public static List<String[]> matchArea(final Map<String, AdminArea[]> map1, |
| final Map<String, AdminArea[]> map2) { |
| /** |
| * List of neighboring areas. This is the result and needs to be turned |
| * into a properties file. |
| */ |
| final List<String[]> outlist = new ArrayList<String[]>(4000); |
| //int counter = 0; |
| String[] entry = null; |
| |
| final Set<String> set1 = map1.keySet(); |
| final int n1 = set1.size(); |
| final String[] keys1 = set1.toArray(new String[n1]); |
| Arrays.sort(keys1); |
| final Set<String> set2 = map2.keySet(); |
| final int n2 = set2.size(); |
| final String[] keys2 = set2.toArray(new String[n2]); |
| Arrays.sort(keys2); |
| for (int i = 0; i < n1; i++) { |
| final String key = keys1[i]; |
| for (int j = 0; j < n2; j++) { |
| final String key2 = keys2[j]; |
| // if ( key.contains("SON") && key2.contains("AZ")){ |
| // debug("** "+key+" : "+key2); |
| // } |
| |
| final boolean neighbor = isNeighbor(map1, map2, key, key2); |
| |
| if (neighbor) { |
| c_Neighbors++; |
| counter++; |
| debug("isNeighbor match: " + key + " " + key2); |
| entry = new String[2]; |
| entry[0] = key; |
| entry[1] = key2; |
| outlist.add(entry); |
| |
| } |
| } |
| } |
| // if (counter == 0) { |
| // debug("Number of Neighbors: "+counter); |
| // } |
| return outlist; |
| } |
| |
| /** |
| * Given a LatLong, generate an array of polygons. |
| * |
| * @param latLong |
| * @return array of Polygons |
| */ |
| private static Polygon[] getPolygons(final LatLong latLong) { |
| |
| final List<Segment> list = latLong.getSegments(); |
| final int numareas = list.size(); |
| final Polygon[] polygons = new Polygon[numareas]; |
| for (int i = 0; i < numareas; i++) { |
| |
| final Segment segment = list.get(i); |
| final Polygon polygon = getPolygon(segment); |
| |
| // debug("Polygon: "+polygon); |
| polygons[i] = polygon; |
| } |
| |
| return polygons; |
| } |
| |
| /** |
| * Given a LatLong Segment generate a polygon. |
| * |
| * @param segment |
| * @return Polygon |
| */ |
| private static Polygon getPolygon(final Segment segment) { |
| |
| final Polygon polygon = new Polygon(); |
| final int size = segment.size(); |
| for (int p = 0; p < size; p++) { |
| final double x = segment.longitude(p); |
| final double y = segment.latitude(p); |
| final int xint = (int) ((x + 180) * 1000); |
| final int yint = (int) ((y + 90) * 1000); |
| polygon.addPoint(xint, yint); |
| } |
| return polygon; |
| } // getPolygon |
| |
| /** |
| * Given the names of 2 administrative areas, determine if they are |
| * neighbors. For each admin area we have one or more bounding boxes. They |
| * may be neighbors if any bounding box is adjacent to any bounding box for |
| * the other admin area. If the bounding box test is true then it is worth |
| * checking if they are really neighbors by checking first if any points on |
| * the borders match. If so they are really neighbors and it returns true. |
| * if not it checks if any point on the border of one area is contained in |
| * the polygon for the other area. |
| * |
| * @param map1 |
| * @param map2 |
| * @param key1 |
| * area name |
| * @param key2 |
| * area name |
| * |
| * @return true if neighboring areas |
| */ |
| |
| private static boolean isNeighbor(final Map<String, AdminArea[]> map1, |
| final Map<String, AdminArea[]> map2, final String key1, |
| final String key2) { |
| boolean neighbor = false; |
| |
| final AdminArea[] areas1 = map1.get(key1); |
| final AdminArea[] areas2 = map2.get(key2); |
| |
| for (final AdminArea area1 : areas1) { |
| for (final AdminArea area2 : areas2) { |
| // provide a place to set breakpoint |
| if (key1.contains("ALB") && key2.contains("GRC")) { |
| debug(key1 + " : " + key2); |
| } |
| neighbor = testAdjacency(area1, area2); // bbox ck |
| if (neighbor) { |
| c_bboxNeighbors++; |
| // debug("*BBox Match: "+key1+" : "+key2); |
| if (bboxCheckOnly) { |
| // debug("*AdminArea Match:"+key1+" : "+key2); |
| // potential neighbor |
| return true; |
| } |
| // Now we know that they are potential neighbors |
| // now check if at least one border point matches |
| neighbor = testAdjacencyMatch(area1, area2); |
| if (!neighbor) { |
| // not found by matching points try containment |
| neighbor = testAdjacencyContains(area1, area2); |
| if (neighbor) { |
| c_segmentNeighbors++; |
| debug("*Border containment Match: " + key1 + " : " |
| + key2); |
| return true; |
| } |
| // keep checking areas |
| } else { |
| // We have neighbors |
| // counting was done in above method |
| return true; |
| } |
| } |
| } |
| } |
| return neighbor; |
| } |
| |
| /** |
| * Test adjacency by containment of any corner of two bounding boxes If any |
| * corner of AdminArea 1 is contained in AdminArea 2 or any corner of |
| * AdminArea 2 is contained in AdminArea 1 then they are adjacent. |
| * |
| * @param bbox1 |
| * bounding box 1 |
| * @param bbox2 |
| * bounding box 2 |
| * @return true if adjacent or containment true |
| */ |
| public static boolean testAdjacency(final AdminArea bbox1, |
| final AdminArea bbox2) { |
| final Rectangle r1 = bbox1.bbox; |
| final Rectangle r2 = bbox2.bbox; |
| // coords of corners of first bounding box |
| final double xMin1 = r1.getMinX(); |
| final double xMax1 = r1.getMaxX(); |
| final double yMin1 = r1.getMinY(); |
| final double yMax1 = r1.getMaxY(); |
| // test overlap of bbox 1 to bbox2 |
| boolean adjacent = true; |
| while (true) { |
| if (r2.contains(xMin1, yMin1)) { |
| break; |
| } |
| if (r2.contains(xMin1, yMax1)) { |
| break; |
| } |
| if (r2.contains(xMax1, yMax1)) { |
| break; |
| } |
| if (r2.contains(xMax1, yMin1)) { |
| break; |
| } |
| adjacent = false; |
| break; |
| } |
| if (!adjacent) { |
| // coords of corners of second bounding box |
| final double xMin2 = r2.getMinX(); |
| final double xMax2 = r2.getMaxX(); |
| final double yMin2 = r2.getMinY(); |
| final double yMax2 = r2.getMaxY(); |
| // test overlap of bbox2 to bbox1 |
| adjacent = true; |
| while (true) { |
| if (r1.contains(xMin2, yMin2)) { |
| break; |
| } |
| if (r1.contains(xMin2, yMax2)) { |
| break; |
| } |
| if (r1.contains(xMax2, yMax2)) { |
| break; |
| } |
| if (r1.contains(xMax2, yMin2)) { |
| break; |
| } |
| adjacent = false; |
| break; |
| } |
| } |
| // if (adjacent) { |
| // debug(" AdminArea match: "+ |
| // bbox1.name+" : "+bbox2.name); |
| // } |
| return adjacent; |
| |
| } |
| |
| /** |
| * Test adjacency by matching the points on the border of each area for a |
| * match |
| * |
| * @param area1 |
| * AdminArea class containing polygon1 |
| * @param area2 |
| * AdminArea class containing polygon2 |
| * @return true if adjacent or containment true |
| */ |
| @SuppressWarnings("boxing") |
| public static boolean testAdjacencyMatch(final AdminArea area1, |
| final AdminArea area2) { |
| boolean adjacent = false; |
| final Segment s1 = area1.segment; |
| final Segment s2 = area2.segment; |
| final String key1 = area1.name; |
| final String key2 = area2.name; |
| final int size1 = s1.size(); |
| final int size2 = s2.size(); |
| // For every point is segment 1 |
| // see if there is a matching point in segment2 |
| for (int i = 0; i < size1; i++) { |
| final double lt = s1.latitude(i); |
| final double ln = s1.longitude(i); |
| for (int j = 0; j < size2; j++) { |
| if (lt == s2.latitude(j) && ln == s2.longitude(j)) { |
| // found a match |
| c_pointNeighbors++; |
| debug("*Border point Match: " + key1 + " : " + key2); |
| |
| adjacent = true; |
| return true; |
| } |
| } |
| } |
| |
| for (int i = 0; i < size1; i++) { |
| final double lt = Math.abs(s1.latitude(i)); |
| final double ln = Math.abs(s1.longitude(i)); |
| for (int j = 0; j < size2; j++) { |
| if (closeTo(lt, ln, Math.abs(s2.latitude(j)), Math.abs(s2 |
| .longitude(j)))) { |
| debug(" closeTo match point: " + key1 + " : " + key2); |
| c_closeNeighbors++; |
| debug(String.format("%f %f %f %f", s1.latitude(i), s1 |
| .longitude(i), s2.latitude(j), s2.longitude(j))); |
| return true; |
| } |
| } |
| } |
| return adjacent; |
| |
| } |
| |
| /** |
| * Check if point1 is close to point 2 by the amount of the delta. A delta |
| * of 0.01 is approx 1.11 km |
| * |
| * @param lt1 |
| * Point 1 latitude |
| * @param ln1 |
| * Point 1 longitude |
| * @param lt2 |
| * point 2 latitude |
| * @param ln2 |
| * point 2 longitude |
| * @return true if within delta. |
| */ |
| |
| public static boolean closeTo(final double lt1, final double ln1, |
| final double lt2, final double ln2) { |
| final double delta = 0.01; |
| |
| if ((lt1 <= lt2 + delta && lt1 >= lt2 - delta) |
| && (ln1 <= ln2 + delta && ln1 >= ln2 - delta)) { |
| |
| return true; |
| } |
| return false; |
| } |
| |
| /** |
| * Test adjacency by checking if any point on the border of one area is |
| * contained in the polygon for the other area |
| * |
| * @param bbox1 |
| * AdminArea class containing polygon1 |
| * @param bbox2 |
| * AdminArea class containing polygon2 |
| * @return true if adjacent or containment true |
| */ |
| public static boolean testAdjacencyContains(final AdminArea bbox1, |
| final AdminArea bbox2) { |
| boolean adjacent = false; |
| final Segment s1 = bbox1.segment; |
| final Segment s2 = bbox2.segment; |
| final String key1 = bbox1.name; |
| final String key2 = bbox2.name; |
| final int size1 = s1.size(); |
| final int size2 = s2.size(); |
| |
| // try bbox2 polygon contains BBox1 points |
| final Polygon polygon2 = getPolygon(s2); |
| for (int i = 0; i < size1; i++) { |
| final double lt = s1.latitude(i); |
| final double ln = s1.longitude(i); |
| final double x = ((ln + 180) * 1000); |
| final double y = ((lt + 90) * 1000); |
| adjacent = polygon2.contains(x, y); |
| if (adjacent) { |
| debug(" segment2 contains point " + i + " " + key1 + " : " |
| + key2); |
| adjacent = true; |
| return true; |
| } |
| } |
| |
| // still not found adjacency, |
| // try bbox1 polygon contains BBox2 points |
| for (int i = 0; i < size2; i++) { |
| final double lt = s2.latitude(i); |
| final double ln = s2.longitude(i); |
| final Polygon polygon1 = getPolygon(s1); |
| final double x = ((ln + 180) * 1000); |
| final double y = ((lt + 90) * 1000); |
| adjacent = polygon1.contains(x, y); |
| |
| if (adjacent) { |
| debug(" segment1 contains point " + i + " " + key1 + " : " |
| + key2); |
| adjacent = true; |
| return true; |
| } |
| } |
| |
| return adjacent; |
| |
| } |
| |
| /** |
| * get the folder to use for output. |
| * |
| * @return Fully qualified path (e.g. c:/tmp/commonborder) |
| */ |
| public static String getPropertyFolder() { |
| |
| String tmpFolder = null; |
| try { |
| final File tmpFile = File.createTempFile("test", ".txt"); |
| tmpFolder = tmpFile.getParent(); |
| if (!tmpFile.delete()) { |
| debug("Warning: Unable to delete temporary file: "+ tmpFile.getAbsolutePath()); |
| } |
| |
| tmpFolder = tmpFolder + "/commonborder/"; |
| } catch (final IOException e) { |
| debug("Unable to determine tmp folder for property files."); |
| debug(e.getMessage()); |
| } |
| return tmpFolder; |
| } |
| |
| /** |
| * print the statistics |
| * |
| */ |
| public static void statistics() { |
| final long doneAt = new Date().getTime() - c_start; |
| System.out.println("* Neighbor Areas based on BoundingBox: " |
| + c_bboxNeighbors); |
| System.out.println("* Neighbor Areas based on Matching points: " |
| + c_pointNeighbors); |
| System.out |
| .println("* Neighbor Areas based on very close Matching points: " |
| + c_closeNeighbors); |
| System.out.println("* Neighbor Areas based on containment: " |
| + c_segmentNeighbors); |
| System.out.println("* Final count of Neighbor Areas: " |
| + c_Neighbors); |
| System.out.println("* Elapsed time: " + doneAt); |
| c_bboxNeighbors = 0; |
| c_pointNeighbors = 0; |
| c_closeNeighbors = 0; |
| c_segmentNeighbors = 0; |
| c_Neighbors = 0; |
| c_start = new Date().getTime(); |
| } |
| |
| /** |
| * print debug messages to the r log. |
| * |
| * @param msg |
| */ |
| public static void debug(final String msg) { |
| if (DEBUG) { |
| System.out.println(getClock() + " " + msg); |
| } |
| } |
| |
| /** |
| * print messages to the log. |
| * |
| * @param msg |
| */ |
| public static void output(final String msg) { |
| |
| System.out.println(getClock() + " " + msg); |
| |
| } |
| |
| /** |
| * debug output for exceptions |
| * |
| * @param msg |
| * @param e |
| */ |
| public static void debug(final String msg, final Throwable e) { |
| // log(msg,e); |
| debug(msg); |
| e.printStackTrace(); |
| } |
| |
| private static String getClock() { |
| final long now = new Date().getTime() - start; |
| return Long.toString(now); |
| } |
| |
| // ************************************************************* |
| // ************************************************************* |
| /** |
| * Class used to hold both the polygon and bounding box so it can be stored |
| * in a HashMap and passed around to methods that need both. |
| * |
| * |
| * |
| */ |
| public static class AdminArea { |
| String name = null; |
| Rectangle bbox = null; |
| Segment segment = null; |
| |
| /** |
| * constructor |
| * |
| * @param segment |
| * Contains the latLong info for polygon |
| * @param bbox |
| * Bounding box for polygon |
| * @param name |
| * Name associated with polygon |
| */ |
| public AdminArea(final Segment segment, final Rectangle bbox, |
| final String name) { |
| this.name = name; |
| this.bbox = bbox; |
| this.segment = segment; |
| |
| } |
| } |
| |
| } |