package org.opentrafficsim.demo.geometry.shape;
import java.awt.Color;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.rmi.RemoteException;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;
import javax.naming.NamingException;
import org.djunits.unit.util.UNITS;
import org.djunits.value.vdouble.scalar.Direction;
import org.djunits.value.vdouble.scalar.Frequency;
import org.djunits.value.vdouble.scalar.Length;
import org.djunits.value.vdouble.scalar.Speed;
import org.geotools.data.FileDataStoreFinder;
import org.geotools.data.shapefile.ShapefileDataStore;
import org.geotools.data.simple.SimpleFeatureCollection;
import org.geotools.data.simple.SimpleFeatureIterator;
import org.geotools.data.simple.SimpleFeatureSource;
import org.geotools.geometry.jts.JTSFactoryFinder;
import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.GeometryFactory;
import org.locationtech.jts.geom.LineString;
import org.locationtech.jts.geom.Point;
import org.opengis.feature.Property;
import org.opengis.feature.simple.SimpleFeature;
import org.opentrafficsim.core.dsol.OTSSimulatorInterface;
import org.opentrafficsim.core.geometry.OTSGeometryException;
import org.opentrafficsim.core.geometry.OTSLine3D;
import org.opentrafficsim.core.geometry.OTSPoint3D;
import org.opentrafficsim.core.network.Link;
import org.opentrafficsim.core.network.LinkType;
import org.opentrafficsim.core.network.LongitudinalDirectionality;
import org.opentrafficsim.core.network.Network;
import org.opentrafficsim.core.network.NetworkException;
import org.opentrafficsim.draw.network.LinkAnimation;
import org.opentrafficsim.draw.road.LaneAnimation;
import org.opentrafficsim.draw.road.ShoulderAnimation;
import org.opentrafficsim.road.network.OTSRoadNetwork;
import org.opentrafficsim.road.network.lane.CrossSectionLink;
import org.opentrafficsim.road.network.lane.Lane;
import org.opentrafficsim.road.network.lane.NoTrafficLane;
import org.opentrafficsim.road.network.lane.OTSRoadNode;
import org.opentrafficsim.road.network.lane.Shoulder;
import org.opentrafficsim.road.network.lane.changing.LaneKeepingPolicy;
import nl.tudelft.simulation.dsol.simulators.SimulatorInterface;
/**
*
* Copyright (c) 2013-2020 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
* BSD-style license. See OpenTrafficSim License.
*
* $LastChangedDate$, @version $Revision$, by $Author$,
* initial version Sep 11, 2014
* @author Alexander Verbraeck
* @author Guus Tamminga
*/
public final class ShapeFileReader implements UNITS
{
/** Do not instantiate this class. */
private ShapeFileReader()
{
// Cannot be instantiated.
}
/**
* @param network Network; Network, the network
* @param shapeFileName String; the nodes shapefile to read
* @param numberType String; ???
* @param returnCentroid boolean; if true only loop through the centroid/zones (in case of mixed nodes and centroids)
* @param allCentroids boolean; if true: the file contains centroids (a centroid file)
* @return map of (shape file) nodes with nodenr as the key
* @throws IOException on error
*/
public static Map readNodes(final Network network, final String shapeFileName, final String numberType,
final boolean returnCentroid, final boolean allCentroids) throws IOException
{
/*-
* the_geom class com.vividsolutions.jts.geom.Point POINT (190599 325650)
* NODENR class java.lang.Long 18
* NAME class java.lang.String
* X class java.lang.Double 190599.0
* Y class java.lang.Double 325650.0
* ...
*/
URL url;
if (new File(shapeFileName).canRead())
{
url = new File(shapeFileName).toURI().toURL();
}
else
{
url = ShapeFileReader.class.getResource(shapeFileName);
}
ShapefileDataStore storeNodes = (ShapefileDataStore) FileDataStoreFinder.getDataStore(url);
Map nodes = new LinkedHashMap<>();
SimpleFeatureSource featureSourceNodes = storeNodes.getFeatureSource();
SimpleFeatureCollection featureCollectionNodes = featureSourceNodes.getFeatures();
SimpleFeatureIterator iterator = featureCollectionNodes.features();
try
{
while (iterator.hasNext())
{
SimpleFeature feature = iterator.next();
Coordinate coordinate = ((Point) feature.getAttribute("the_geom")).getCoordinate();
String nr = removeQuotes(String.valueOf(feature.getAttribute(numberType)));
boolean addThisNode = false;
if (returnCentroid)
{
if (nr.substring(0, 1).equals("C") || allCentroids)
{
addThisNode = true;
}
}
else
{
if (nr == null)
{
System.out.println("null found");
}
if (!nr.substring(0, 1).equals("C"))
{
addThisNode = true;
}
}
if (addThisNode)
{
OTSRoadNode node =
new OTSRoadNode(network, nr, new OTSPoint3D(coordinate), Direction.instantiateSI(Double.NaN));
nodes.put(nr, node);
}
}
}
catch (Exception problem)
{
problem.printStackTrace();
}
finally
{
iterator.close();
storeNodes.dispose();
}
System.out.println("aantal knopen (353): geteld " + nodes.size());
return nodes;
}
/**
* @param number String; number string
* @return boolean: true if the number refers to a Centroid; false otherwise
*/
public static boolean inspectNodeCentroid(final String number)
{
boolean isCentroid = false;
String[] names = removeQuotes(number).split(":");
String name = names[0];
if (name.charAt(0) == 'C')
{
isCentroid = true;
}
return isCentroid;
}
/**
* @param network OTSRoadNetwork; the network
* @param shapeFileName String; the nodes shapefile to read
* @param links Map<String,Link>; : returns the file with real links
* @param nodes Map<String,OTSNode>; the map of nodes to retrieve start and end node
* @param simulator OTSSimulatorInterface; simulator for the animation registration
* @throws IOException on error
*/
public static void readLinks(final OTSRoadNetwork network, final String shapeFileName, final Map links,
final Map nodes, final OTSSimulatorInterface simulator) throws IOException
{
/*-
* the_geom class com.vividsolutions.jts.geom.MultiLineString MULTILINESTRING ((232250.38755446894 ...
* LINKNR class java.lang.Long 1
* NAME class java.lang.String
* DIRECTION class java.lang.Long 1
* LENGTH class java.lang.Double 1.80327678
* ANODE class java.lang.Long 684088
* BNODE class java.lang.Long 1090577263
* LINKTAG class java.lang.String 967536
* WEGTYPEAB class java.lang.String mvt
* TYPEWEGVAB class java.lang.String asw 2x2 (8600)
* TYPEWEG_AB class java.lang.String 12 Autosnelweg 2x2
* SPEEDAB class java.lang.Double 120.0
* CAPACITYAB class java.lang.Double 8600.0
* ...
*/
URL url;
if (new File(shapeFileName).canRead())
{
url = new File(shapeFileName).toURI().toURL();
}
else
{
url = ShapeFileReader.class.getResource(shapeFileName);
}
ShapefileDataStore storeLinks = (ShapefileDataStore) FileDataStoreFinder.getDataStore(url);
SimpleFeatureSource featureSourceLinks = storeLinks.getFeatureSource();
SimpleFeatureCollection featureCollectionLinks = featureSourceLinks.getFeatures();
SimpleFeatureIterator iterator = featureCollectionLinks.features();
try
{
while (iterator.hasNext())
{
SimpleFeature feature = iterator.next();
GeometryFactory geometryFactory = JTSFactoryFinder.getGeometryFactory();
Geometry geometry = (Geometry) feature.getAttribute("the_geom");
Coordinate[] coords = geometry.getCoordinates();
LineString line = geometryFactory.createLineString(coords);
String nr = String.valueOf(feature.getAttribute("LINKNR"));
String nrBA = nr + "_BA";
String name = String.valueOf(feature.getAttribute("NAME"));
// the reason to use String.valueOf(...) is that the .dbf files sometimes use double,
// but also represent LENGTH by a string ....
double lengthIn = Double.parseDouble(String.valueOf(feature.getAttribute("LENGTH")));
Length length = new Length(lengthIn, KILOMETER);
short direction = (short) Long.parseLong(String.valueOf(feature.getAttribute("DIRECTION")));
String lNodeA = String.valueOf(feature.getAttribute("ANODE"));
String lNodeB = String.valueOf(feature.getAttribute("BNODE"));
// long lNodeB = NodeCentroidNumber(String.valueOf(feature.getAttribute("BNODE")));
String linkTag = (String) feature.getAttribute("LINKTAG");
String wegtype = (String) feature.getAttribute("WEGTYPEAB");
String typeWegVak = (String) feature.getAttribute("TYPEWEGVAB");
String typeWeg = (String) feature.getAttribute("TYPEWEG_AB");
Double speedIn = Double.parseDouble(String.valueOf(feature.getAttribute("SPEEDAB")));
Speed speed = new Speed(speedIn, KM_PER_HOUR);
double capacityIn = Double.parseDouble(String.valueOf(feature.getAttribute("CAPACITYAB")));
Frequency capacity = new Frequency(capacityIn, PER_HOUR);
// new DoubleScalar.Abs(shpLink.getLength(), KILOMETER);
// create the link or connector to a centroid....
OTSRoadNode nodeA = nodes.get(lNodeA);
OTSRoadNode nodeB = nodes.get(lNodeB);
if (nodeA != null && nodeB != null)
{
CrossSectionLink linkAB = null;
CrossSectionLink linkBA = null;
linkAB = new CrossSectionLink(network, nr, nodeA, nodeB, network.getLinkType(LinkType.DEFAULTS.ROAD),
new OTSLine3D(new OTSPoint3D[] {nodeA.getPoint(), nodeB.getPoint()}), LaneKeepingPolicy.KEEPRIGHT);
animate(linkAB, typeWegVak, simulator);
linkBA = new CrossSectionLink(network, nrBA, nodeB, nodeA, network.getLinkType(LinkType.DEFAULTS.ROAD),
new OTSLine3D(new OTSPoint3D[] {nodeB.getPoint(), nodeA.getPoint()}), LaneKeepingPolicy.KEEPRIGHT);
animate(linkBA, typeWegVak, simulator);
if (direction == 1)
{
links.put(nr, linkAB);
}
else if (direction == 2)
{
links.put(nrBA, linkBA);
}
else if (direction == 3)
{
links.put(nr, linkAB);
links.put(nrBA, linkBA);
}
}
else
{
System.out.println("Node lNodeA=" + lNodeA + " or lNodeB=" + lNodeB + " not found for linknr=" + nr
+ ", name=" + name);
}
}
}
catch (Exception problem)
{
problem.printStackTrace();
}
finally
{
iterator.close();
storeLinks.dispose();
}
}
/**
* @param shapeFileName String; the areas shapefile to read
* @throws IOException on error
*/
public static void shapeFileInfo(final String shapeFileName) throws IOException
{
URL url;
if (new File(shapeFileName).canRead())
{
url = new File(shapeFileName).toURI().toURL();
}
else
{
url = ShapeFileReader.class.getResource(shapeFileName);
}
ShapefileDataStore store = (ShapefileDataStore) FileDataStoreFinder.getDataStore(url);
SimpleFeatureSource featureSource = store.getFeatureSource();
SimpleFeatureCollection featureCollection = featureSource.getFeatures();
SimpleFeatureIterator iterator = featureCollection.features();
try
{
while (iterator.hasNext())
{
SimpleFeature feature = iterator.next();
Collection areaProperties = feature.getProperties();
for (Property p : areaProperties)
{
System.out.println(p.getName() + " " + p.getValue().getClass() + " " + p.getValue().toString());
}
return;
}
}
catch (Exception problem)
{
problem.printStackTrace();
}
finally
{
iterator.close();
store.dispose();
}
}
/**
* @param name String; the name with quotes
* @return name without quotes
*/
public static String removeQuotes(final String name)
{
String newName = name;
if (newName.length() >= 2 && newName.charAt(0) == '"' && newName.charAt(newName.length() - 1) == '"')
{
newName = newName.substring(1, newName.length() - 1);
}
return newName;
}
/**
* @param link CrossSectionLink; the link
* @param wegType String; wegtype
* @param simulator SimulatorInterface.TimeDoubleUnit; animator
* @throws NamingException in case of context error
* @throws RemoteException in case of context error
* @throws NetworkException on network inconsistency
*/
private static void animate(final CrossSectionLink link, final String wegType,
final SimulatorInterface.TimeDoubleUnit simulator) throws NamingException, NetworkException, RemoteException
{
// leave out if center line not needed.
new LinkAnimation(link, simulator, 0.1f);
if (wegType.startsWith("asw") || wegType.startsWith("80"))
{
int spits = 0;
int n = 1;
if (wegType.contains("2x2"))
{
n = 2;
}
if (wegType.contains("2x3"))
{
n = 3;
}
if (wegType.contains("2x4"))
{
n = 4;
}
if (wegType.contains("2x5"))
{
n = 5;
}
if (wegType.contains("+ 1") || wegType.contains("+1"))
{
spits = 1;
}
if (wegType.contains("+ 2") || wegType.contains("+2"))
{
spits = 2;
}
addNLanes(n, spits, link, simulator);
}
if (wegType.startsWith("stads"))
{
int n = 1;
if (wegType.contains("2x2"))
{
n = 2;
}
if (wegType.contains("2x3"))
{
n = 3;
}
boolean middenberm = wegType.contains("met middenberm");
addCityStreetLanes(n, middenberm, link, simulator);
}
else
{
addCityStreet(link, simulator);
}
}
/**
* @param n int; aantal stroken per zijde
* @param spits int; aantal spitsstroken
* @param link CrossSectionLink; link
* @param simulator SimulatorInterface.TimeDoubleUnit; animator
* @throws NetworkException on network inconsistency
*/
private static void addNLanes(final int n, final int spits, final CrossSectionLink link,
final SimulatorInterface.TimeDoubleUnit simulator) throws NetworkException
{
// 2 x n lanes, grass underneath, lines between lanes, barrier in center
// lane is 3.5 meters wide. gap in middle is one meter. outside 0.5 meters on both sides
Length m05 = new Length(0.5, METER);
Length m10 = new Length(1.0, METER);
Length m35 = new Length(3.5, METER);
Speed speedLimit = new Speed(100, KM_PER_HOUR);
try
{
// middenberm
Shoulder sM = new Shoulder(link, "sM", new Length(0.0, METER), m10);
new ShoulderAnimation(sM, simulator, Color.GREEN);
for (int i = -1; i <= 1; i += 2)
{
LongitudinalDirectionality dir =
(i < 0) ? LongitudinalDirectionality.DIR_PLUS : LongitudinalDirectionality.DIR_MINUS;
// TODO Lane doesn't take direction anymore, this is given in the LaneType
String lr = i < 0 ? "L" : "R";
//
Lane laneEM = new NoTrafficLane(link, lr + "." + "EM", new Length(i * 0.75, METER), new Length(i * 0.75, METER),
m05, m05);
new LaneAnimation(laneEM, simulator, Color.LIGHT_GRAY);
double lat = 1;
for (int j = 0; j < n; j++)
{
lat += i * 1.75;
Lane lane = new Lane(link, "lane." + lr + "." + j, new Length(lat, METER), new Length(lat, METER), m35, m35,
null, speedLimit);
new LaneAnimation(lane, simulator, Color.GRAY);
lat += i * 1.75;
}
// spitsstroken
for (int j = 0; j < spits; j++)
{
lat += i * 1.75;
Lane lane = new NoTrafficLane(link, "extra." + lr + "." + j, new Length(lat, METER), new Length(lat, METER),
m35, m35);
new LaneAnimation(lane, simulator, Color.LIGHT_GRAY);
lat += i * 1.75;
}
Lane laneEO = new NoTrafficLane(link, lr + "." + "EO", new Length(lat + i * 0.25, METER),
new Length(lat + i * 0.25, METER), m05, m05);
new LaneAnimation(laneEO, simulator, Color.LIGHT_GRAY);
lat += i * 0.5;
Shoulder sO = new Shoulder(link, lr + "." + "sO", new Length(lat, METER), m10);
new ShoulderAnimation(sO, simulator, Color.GREEN);
}
}
catch (NamingException | RemoteException | OTSGeometryException ne)
{
//
}
}
/**
* @param n int; aantal stroken per zijde
* @param middenberm boolean; aanwezig of niet
* @param link CrossSectionLink; link
* @param simulator SimulatorInterface.TimeDoubleUnit; animator
* @throws NetworkException on network inconsistency
*/
private static void addCityStreetLanes(final int n, final boolean middenberm, final CrossSectionLink link,
final SimulatorInterface.TimeDoubleUnit simulator) throws NetworkException
{
// 2 x n lanes, grass underneath, lines between lanes, barrier in center
// lane is 3.0 meters wide. gap in middle is one meter. outside 0.5 meters on both sides
Length m10 = new Length(1.0, METER);
Length m30 = new Length(3.0, METER);
Speed speedLimit = new Speed(100, KM_PER_HOUR);
try
{
if (middenberm)
{
Shoulder sM = new Shoulder(link, "sM", new Length(0.0, METER), m10);
new ShoulderAnimation(sM, simulator, Color.GREEN);
}
for (int i = -1; i <= 1; i += 2)
{
// TODO Lane doesn't take direction anymore, this is given in the LaneType
LongitudinalDirectionality dir =
(i < 0) ? LongitudinalDirectionality.DIR_PLUS : LongitudinalDirectionality.DIR_MINUS;
double lat = middenberm ? 0.5 : 0.0;
for (int j = 0; j < n; j++)
{
lat += i * 1.5;
String lr = i < 0 ? "L" : "R";
Lane lane = new Lane(link, "lane." + lr + "." + j, new Length(lat, METER), new Length(lat, METER), m30, m30,
null, speedLimit);
new LaneAnimation(lane, simulator, Color.DARK_GRAY);
lat += i * 1.5;
}
}
}
catch (NamingException | RemoteException | ArrayIndexOutOfBoundsException | OTSGeometryException ne)
{
ne.printStackTrace();
}
}
/**
* @param link CrossSectionLink; link
* @param simulator SimulatorInterface.TimeDoubleUnit; animator
* @throws NetworkException on network inconsistency
*/
private static void addCityStreet(final CrossSectionLink link, final SimulatorInterface.TimeDoubleUnit simulator)
throws NetworkException
{
Length m60 = new Length(6.0, METER);
Speed speedLimit = new Speed(100, KM_PER_HOUR);
try
{
Lane lane = new Lane(link, "lane", new Length(0.0, METER), new Length(0.0, METER), m60, m60, null, speedLimit);
new LaneAnimation(lane, simulator, Color.DARK_GRAY);
}
catch (NamingException | RemoteException | OTSGeometryException ne)
{
//
}
}
}