// ********************************************************************** // // <copyright> // // BBN Technologies // 10 Moulton Street // Cambridge, MA 02138 // (617) 873-8000 // // Copyright (C) BBNT Solutions LLC. All rights reserved. // // </copyright> // ********************************************************************** // // $Source: /cvs/distapps/openmap/src/openmap/com/bbn/openmap/layer/vpf/VMAP2Shape.java,v $ // $RCSfile: VMAP2Shape.java,v $ // $Revision: 1.8 $ // $Date: 2009/01/21 01:24:41 $ // $Author: dietrick $ // // ********************************************************************** package com.bbn.openmap.layer.vpf; import java.io.FileInputStream; import java.io.IOException; import java.util.Properties; import com.bbn.openmap.MoreMath; import com.bbn.openmap.dataAccess.shape.EsriShapeExport; import com.bbn.openmap.dataAccess.shape.ShapeUtils; import com.bbn.openmap.layer.shape.ShapeFile; import com.bbn.openmap.omGraphics.OMGraphic; import com.bbn.openmap.omGraphics.OMGraphicList; import com.bbn.openmap.omGraphics.OMPoly; import com.bbn.openmap.omGraphics.SinkGraphic; import com.bbn.openmap.proj.DrawUtil; import com.bbn.openmap.proj.ProjMath; import com.bbn.openmap.proj.Projection; import com.bbn.openmap.proj.coords.LatLonPoint; import com.bbn.openmap.util.PropUtils; /** * Convert NIMA VMAP geospatial data into ESRI shapefile format. */ public class VMAP2Shape { protected static final String DEF_VMAP_TYPE = "bnd"; protected static final String DEF_PROPS_FILE_NAME = System.getProperty("user.home") + System.getProperty("file.separator") + "openmap.properties"; protected static final String DEF_PREFIX = "vmapref"; protected static final boolean DEF_DO_THINNING = false; protected static final float DEF_FAN_EPS = 0.1f; protected static final float DEF_ZERO_EPS = 0.0001f; protected static final float DEF_THRESHOLD = 0.5f; protected String vmaptype = DEF_VMAP_TYPE; protected String propsFileName = DEF_PROPS_FILE_NAME; protected String prefix = DEF_PREFIX; protected boolean doThinning = DEF_DO_THINNING; protected float fan_eps = DEF_FAN_EPS; protected float zero_eps = DEF_ZERO_EPS; protected float threshold = DEF_THRESHOLD; protected LibrarySelectionTable lst; protected transient LayerGraphicWarehouseSupport warehouse; protected boolean joinPolylines = false; public VMAP2Shape() {} /** * Will load the properties set in the VPF2Shape object, fetch the * OMGraphics from the VPF database, and write the shape file. * * @param shapeFileName */ public void writeShapeFile(String shapeFileName) { setProperties(prefix, loadProperties()); writeShapeFile(shapeFileName, getRectangle()); } /** * Write the shape file, assumes that the properties have been loaded and * the graphics fetched from the VPF database. * * @param shapeFileName the file name to write the shapes into. * @param graphics OMGraphics from VPF database to write to shape file. */ public void writeShapeFile(String shapeFileName, OMGraphicList graphics) { try { ShapeFile s = new ShapeFile(shapeFileName); int nGraphics = graphics.size(); if (nGraphics > 0) { OMGraphic omg = graphics.get(0); if ((omg instanceof OMPoly) && (omg.getRenderType() == OMGraphic.RENDERTYPE_LATLON)) { int shapeType = ((OMPoly) omg).isPolygon() ? ShapeUtils.SHAPE_TYPE_POLYGON : ShapeUtils.SHAPE_TYPE_ARC; System.out.println("shapeType=" + shapeType); s.setShapeType(shapeType); } } System.out.println(nGraphics + " candidates."); if (doThinning) { OMGraphicList saveGraphics = new OMGraphicList(); for (int i = 0; i < nGraphics; i++) { OMGraphic omg = graphics.get(i); if ((omg instanceof OMPoly) && (omg.getRenderType() == OMGraphic.RENDERTYPE_LATLON)) { OMPoly poly = (OMPoly) omg; if (maybeThrowAwayPoly(poly)) { continue; } saveGraphics.add(poly); } else { System.out.println("Skipping candidate: " + omg.getClass().toString() + ", " + omg.getRenderType()); } } graphics = saveGraphics; if (joinPolylines) { nGraphics = graphics.size(); System.out.println("Joining " + nGraphics + " polyline candidates."); graphics = joinCommonLines(graphics, zero_eps); } } // Using com.bbn.openmap.dataAccess.shape package to write // shape file: EsriShapeExport ese = new EsriShapeExport(graphics, (Projection) null, shapeFileName); ese.export(); // Instead of using com.bbn.openmap.layer.shape package to // write shape file: // int nDumped = 0; // nGraphics = graphics.size(); // System.out.println("Dumping " + nGraphics + " // graphics."); // for (int i = 0; i < nGraphics; i++) { // OMPoly poly = (OMPoly) graphics.getOMGraphicAt(i); // float[] radians = poly.getLatLonArray(); // ESRIPolygonRecord epr = new ESRIPolygonRecord(); // epr.add(radians); // epr.setPolygon(poly.isPolygon());//set POLYGON vs ARC // s.add(epr); // ++nDumped; // } // // s.verify(true, true); // s.verify(true, true); // s.close(); // System.out.println("Wrote " + nDumped + " Graphics."); } catch (java.io.IOException e) { e.printStackTrace(); } } /** * Iterates through graphic list finding non-connected polylines. iterates * over these to find lines with common endpoints and joining them. * * @param list * @param zero_eps * @return OMGraphicList of polylines of joined segments */ protected static OMGraphicList joinCommonLines(OMGraphicList list, float zero_eps) { int size = list.size(); int len1, len2; double lat1, lon1, lat2, lon2; OMGraphic obj; OMGraphicList newGraphics = new OMGraphicList(); OMGraphicList plineGraphics = new OMGraphicList(); // check for non-connected polylines System.out.println("finding polylines..."); for (int i = 0; i < size; i++) { obj = list.getOMGraphicAt(i); if ((obj instanceof OMPoly) && !((OMPoly) obj).isPolygon()) { plineGraphics.add(obj); } else { newGraphics.add(obj); } } // iterate through the polylines and join lines with common // endpoints size = plineGraphics.size(); OMPoly poly1, poly2; double[] rads1, rads2, radians; System.out.println("maybe joining " + size + " polylines..."); // nasty!: > O(n^2) for (int i = 0; i < size; i++) { if (i % 500 == 0) { System.out.println("checking pline i=" + i); } for (int j = 0; j < size; j++) { if (i == j) { continue; } obj = plineGraphics.getOMGraphicAt(i); if (obj instanceof SinkGraphic) { continue; } poly1 = (OMPoly) obj; rads1 = poly1.getLatLonArray(); len1 = rads1.length; lat1 = ProjMath.radToDeg(rads1[len1 - 2]); lon1 = ProjMath.radToDeg(rads1[len1 - 1]); obj = plineGraphics.getOMGraphicAt(j); if (obj instanceof SinkGraphic) { continue; } poly2 = (OMPoly) obj; rads2 = poly2.getLatLonArray(); len2 = rads2.length; lat2 = ProjMath.radToDeg(rads2[0]); lon2 = ProjMath.radToDeg(rads2[1]); if (MoreMath.approximately_equal(lat1, lat2, zero_eps) && MoreMath.approximately_equal(lon1, lon2, zero_eps)) { // System.out.println("joining..."); radians = new double[len1 + len2 - 2]; System.arraycopy(rads1, 0, radians, 0, len1); System.arraycopy(rads2, 0, radians, len1 - 2, len2); poly1.setLocation(radians, OMGraphic.RADIANS); plineGraphics.setOMGraphicAt(SinkGraphic.getSharedInstance(), j); j = -1;// redo search } } } // add the joined lines back to the data set size = plineGraphics.size(); for (int i = 0; i < size; i++) { obj = plineGraphics.getOMGraphicAt(i); if (obj instanceof OMPoly) { newGraphics.add(obj); } } return newGraphics; } /** traverse array and coalesce adjacent points which are the same */ public static double[] coalesce_points(double[] radians, double eps, boolean ispolyg) { int write = 2; int len = radians.length; for (int i = write - 2, j = write; j < len; j += 2) { double lat1 = ProjMath.radToDeg(radians[i]); double lon1 = ProjMath.radToDeg(radians[i + 1]); double lat2 = ProjMath.radToDeg(radians[j]); double lon2 = ProjMath.radToDeg(radians[j + 1]); if (MoreMath.approximately_equal(lat1, lat2, eps) && MoreMath.approximately_equal(lon1, lon2, eps)) { continue; } i = write; radians[write++] = radians[j]; radians[write++] = radians[j + 1]; } // check for mid-phase line if (ispolyg && (write == 6) && MoreMath.approximately_equal(radians[0], radians[4], eps) && MoreMath.approximately_equal(radians[1], radians[5], eps)) { write -= 2;// eliminate wrapped vertex } double[] newrads = new double[write]; System.arraycopy(radians, 0, newrads, 0, write); return newrads; } /** return true if we should throw away the poly */ protected boolean maybeThrowAwayPoly(OMPoly poly) { double[] radians = poly.getLatLonArray(); double lat, lon, thresh = ProjMath.degToRad(threshold); radians = coalesce_points(radians, 0.0001f, poly.isPolygon()); poly.setLocation(radians, OMGraphic.RADIANS);// install new if (radians.length < 4) { return true;// throw away } if (poly.isPolygon() && (radians.length < 6)) { return true; } int len = radians.length; double d; for (int i = 0; i < len; i += 2) { // test for proximity to 1-degree marks. this hopefully // avoids the problem of throwing away tiled slivers. // (don't throw away poly) lat = ProjMath.radToDeg(radians[i]); lon = ProjMath.radToDeg(radians[i + 1]); if (MoreMath.approximately_equal(lat, (float) (Math.round(lat)), zero_eps)) { return false; } if (MoreMath.approximately_equal(lon, (float) (Math.round(lon)), zero_eps)) { return false; } // check to see if all points fit within a certain // threshold. this should eliminate small islands and // countries like Luxembourg. sorry. for (int j = i + 2; j < radians.length; j += 2) { d = DrawUtil.distance(radians[i], radians[i + 1], radians[j], radians[j + 1]); // outside threshold, don't throw away if (!MoreMath.approximately_equal(d, 0f, thresh)) { return false; } } } if (poly.isPolygon()) { return true;// throw away } // throw away polyline if it's connected (island) return (MoreMath.approximately_equal(ProjMath.radToDeg(radians[0]), ProjMath.radToDeg(radians[radians.length - 2]), zero_eps) && MoreMath.approximately_equal(ProjMath.radToDeg(radians[1]), ProjMath.radToDeg(radians[radians.length - 1]), zero_eps)); } protected Properties loadProperties() { Properties props = new Properties(); try { props.load(new FileInputStream(propsFileName)); } catch (IOException e) { e.printStackTrace(); System.exit(1); } return props; } protected void setProperties(String prefix, Properties props) { String realPrefix = PropUtils.getScopedPropertyPrefix(prefix); String[] paths = PropUtils.initPathsFromProperties(props, realPrefix + VPFLayer.pathProperty); String defaultProperty = props.getProperty(realPrefix + VPFLayer.defaultLayerProperty); if (defaultProperty != null) { System.out.println("defaultProperty=" + defaultProperty); realPrefix = defaultProperty + "."; props = VPFUtil.getDefaultProperties(); } String coverage = props.getProperty(realPrefix + VPFLayer.coverageTypeProperty); if (coverage != null) { vmaptype = coverage; System.out.println("vmaptype=" + vmaptype); } initLST(paths); if (lst.getDatabaseName().equals("DCW")) { System.out.println("creating VPFLayerDCWWarehouse"); warehouse = new VPFLayerDCWWarehouse(); } else { System.out.println("creating VPFLayerGraphicWarehouse"); warehouse = new VPFLayerGraphicWarehouse(); } LayerGraphicWarehouseSupport.setDoThinning(doThinning); LayerGraphicWarehouseSupport.setFanEpsilon(fan_eps); warehouse.setProperties(realPrefix, props); } protected void initLST(String[] paths) { try { if (lst == null) { lst = new LibrarySelectionTable(paths); } } catch (com.bbn.openmap.io.FormatException f) { throw new java.lang.IllegalArgumentException(f.getMessage()); } } public OMGraphicList getRectangle() { int scale = 30000000; int width = 640; int height = 480; LatLonPoint upperLeft = new LatLonPoint.Double(90.0, -180.0); LatLonPoint lowerRight = new LatLonPoint.Double(-90.0, 180.0); warehouse.clear(); System.out.println("VMAP2Shape.getRectangle(): " + "calling drawTile with boundaries: " + upperLeft + lowerRight); long start = System.currentTimeMillis(); lst.drawTile(scale, width, height, vmaptype, warehouse, upperLeft, lowerRight); long stop = System.currentTimeMillis(); System.out.println("VMAP2Shape.getRectangle(): read time: " + ((stop - start) / 1000d) + " seconds"); return warehouse.getGraphics(); } public boolean isDoThinning() { return doThinning; } public void setDoThinning(boolean doThinning) { this.doThinning = doThinning; } public float getFan_eps() { return fan_eps; } public void setFan_eps(float fan_eps) { this.fan_eps = fan_eps; } public LibrarySelectionTable getLst() { return lst; } public void setLst(LibrarySelectionTable lst) { this.lst = lst; } public String getPrefix() { return prefix; } public void setPrefix(String prefix) { this.prefix = prefix; } public String getPropsFileName() { return propsFileName; } public void setPropsFileName(String propsFileName) { this.propsFileName = propsFileName; } public float getThreshold() { return threshold; } public void setThreshold(float threshold) { this.threshold = threshold; } public String getVmaptype() { return vmaptype; } public void setVmaptype(String vmaptype) { this.vmaptype = vmaptype; } public LayerGraphicWarehouseSupport getWarehouse() { return warehouse; } public void setWarehouse(LayerGraphicWarehouseSupport warehouse) { this.warehouse = warehouse; } public float getZero_eps() { return zero_eps; } public void setZero_eps(float zero_eps) { this.zero_eps = zero_eps; } public static void usage() { System.out.println("Usage: java VMAP2Shape [args] <outfile.shp>"); System.out.println("Arguments:"); System.out.println("\t-props <path> path to properties file"); System.out.println(" default: " + DEF_PROPS_FILE_NAME); System.out.println("\t-prefix <identifier> vmap properties prefix"); System.out.println(" default: " + DEF_PREFIX); System.out.println("\t-thin <eps> <thresh> do thinning"); System.out.println(" default eps=" + DEF_FAN_EPS + " thresh=" + DEF_THRESHOLD); System.exit(1); } public static void main(String args[]) { if ((args.length == 0) || ((args.length == 1) && (args[0].startsWith("-")))) { usage(); } com.bbn.openmap.util.Debug.init(System.getProperties()); VMAP2Shape c = new VMAP2Shape(); for (int i = 0; i < args.length - 1; i++) { if (args[i].equalsIgnoreCase("-props")) { c.setPropsFileName(args[++i]); } else if (args[i].equalsIgnoreCase("-prefix")) { c.setPrefix(args[++i]); } else if (args[i].equalsIgnoreCase("-thin")) { c.setDoThinning(true); c.setFan_eps(Float.valueOf(args[++i]).floatValue()); c.setThreshold(Float.valueOf(args[++i]).floatValue()); } else { usage(); } } c.writeShapeFile(args[args.length - 1]); } }