package nl.tudelft.simulation.dsol.animation.gis.esri; import java.awt.Color; import java.awt.Dimension; import java.io.IOException; import java.net.URL; import java.util.ArrayList; import java.util.List; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import org.djutils.draw.bounds.Bounds2d; import org.djutils.io.URLResource; import org.w3c.dom.DOMException; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.SAXException; import nl.tudelft.simulation.dsol.animation.gis.GisMapInterface; import nl.tudelft.simulation.dsol.animation.gis.LayerInterface; import nl.tudelft.simulation.dsol.animation.gis.MapImageInterface; import nl.tudelft.simulation.dsol.animation.gis.MapUnits; import nl.tudelft.simulation.dsol.animation.gis.map.Feature; import nl.tudelft.simulation.dsol.animation.gis.map.GisMap; import nl.tudelft.simulation.dsol.animation.gis.map.Layer; import nl.tudelft.simulation.dsol.animation.gis.map.MapImage; import nl.tudelft.simulation.dsol.animation.gis.transform.CoordinateTransform; /** * This class parses an XML file that defines which elements of shape file(s) need to be drawn and what format to use. *
* Copyright (c) 2020-2022 Delft University of Technology, Jaffalaan 5, 2628 BX Delft, the Netherlands. All rights reserved. See * for project information DSOL Manual. The DSOL * project is distributed under a three-clause BSD-style license, which can be found at * DSOL License. *
** The dsol-animation-gis project is based on the gisbeans project that has been part of DSOL since 2002, originally by Peter * Jacobs and Paul Jacobs. *
* @author Alexander Verbraeck */ public final class EsriFileXmlParser { /** the default mapfile. */ public static final URL MAPFILE_SCHEMA = URLResource.getResource("/resources/mapfile.xsd"); /** Utility class, no constructor. */ private EsriFileXmlParser() { // Utility class } /** * parses a Mapfile URL to a mapFile. * @param url URL; the mapfile url. * @return MapInterface the parsed mapfile. * @throws IOException on failure */ public static GisMapInterface parseMapFile(final URL url) throws IOException { return parseMapFile(url, new CoordinateTransform.NoTransform()); } /** * parses a Mapfile URL to a mapFile. * @param url URL; the mapfile url. * @param coordinateTransform CoordinateTransform; the transformation of (x, y) coordinates to (x', y') coordinates. * @return MapInterface the parsed mapfile. * @throws IOException on failure */ public static GisMapInterface parseMapFile(final URL url, final CoordinateTransform coordinateTransform) throws IOException { try { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); DocumentBuilder builder = factory.newDocumentBuilder(); Document document = builder.parse(url.openStream()); document.getDocumentElement().normalize(); Element root = document.getDocumentElement(); GisMapInterface map = new GisMap(); // map.name map.setName(nodeText(root, "name")); // map.units if (nodeTagExists(root, "units")) { map.setUnits(parseUnits(nodeText(root, "units"))); } // map.extent map.setExtent(parseExtent(nodeTagItem(root, "extent", 0), coordinateTransform)); // map.image if (nodeTagExists(root, "image")) { map.setImage(parseImage(nodeTagItem(root, "image", 0))); } // map.layer map.setLayers(parseLayers(root.getElementsByTagName("layer"), coordinateTransform)); return map; } catch (DOMException | SAXException | ParserConfigurationException exception) { throw new IOException(exception); } } /** * Parses a xml-element representing the units for the map. * @param units String; the string representation of the units * @return MapUnits enum */ @SuppressWarnings("checkstyle:needbraces") private static MapUnits parseUnits(final String units) { if (units.equals("feet")) return MapUnits.FEET; if (units.equals("dd")) return MapUnits.DECIMAL_DEGREES; if (units.equals("inches")) return MapUnits.INCHES; if (units.equals("kilometers")) return MapUnits.KILOMETERS; if (units.equals("meters")) return MapUnits.METERS; if (units.equals("miles")) return MapUnits.MILES; return MapUnits.METERS; } /** * Creates the extent for the map, in transformed units. * @param node Node; the dom node * @param coordinateTransform CoordinateTransform; the transformation to apply on the coordinates * @return Bounds2d; the extent for the map, in transformed units * @throws IOException on parsing error */ private static Bounds2d parseExtent(final Node node, final CoordinateTransform coordinateTransform) throws IOException { try { double minX = nodeDouble(node, "minX"); double minY = nodeDouble(node, "minY"); double maxX = nodeDouble(node, "maxX"); double maxY = nodeDouble(node, "maxY"); double[] p = coordinateTransform.doubleTransform(minX, minY); double[] q = coordinateTransform.doubleTransform(maxX, maxY); minX = Math.min(p[0], q[0]); minY = Math.min(p[1], q[1]); maxX = Math.max(p[0], q[0]); maxY = Math.max(p[1], q[1]); return new Bounds2d(minX, maxX, minY, maxY); } catch (Exception exception) { throw new IOException(exception); } } /** * parses a xml-element representing the Image. * @param node Node; the map.image dom node * @return information about the image * @throws IOException on parsing error */ @SuppressWarnings("checkstyle:needbraces") private static MapImageInterface parseImage(final Node node) throws IOException { Element element = (Element) node; MapImageInterface mapImage = new MapImage(); try { if (nodeTagExists(node, "backgroundColor")) mapImage.setBackgroundColor(parseColor(nodeTagItem(element, "backgroundColor", 0))); if (nodeTagExists(node, "size")) mapImage.setSize(parseDimension(nodeTagItem(element, "size", 0))); return mapImage; } catch (Exception exception) { throw new IOException(exception); } } /** * parses a xml-element representing a Color. * @param node Node; the node to parse for the color * @return Color of element * @throws IOException on parsing error */ private static Color parseColor(final Node node) throws IOException { try { int r = nodeInt(node, "r"); int g = nodeInt(node, "g"); int b = nodeInt(node, "b"); if (nodeTagExists(node, "a")) { int a = nodeInt(node, "a"); return new Color(r, g, b, a); } return new Color(r, g, b); } catch (Exception exception) { throw new IOException(exception.getMessage()); } } /** * Parse an xml-element representing a Dimension. * @param node Node; the dom node with the dimension information * @return Dimension of element * @throws IOException on parsing error */ private static Dimension parseDimension(final Node node) throws IOException { try { int width = nodeInt(node, "width"); int height = nodeInt(node, "height"); return new Dimension(width, height); } catch (Exception exception) { throw new IOException(exception.getMessage()); } } /** * Parse an xml-element representing a Layer. * @param layerNodeList NodeList; the list of layer tags in the map * @param coordinateTransform CoordinateTransform; the transformation to apply to the layer * @return List<LayerInterface>; the list of parsed layers * @throws IOException on parsing error */ private static List