MATSIM
LandmarkerPieSlices.java
Go to the documentation of this file.
1 /* *********************************************************************** *
2  * project: org.matsim.*
3  * LandmarkerPieSlices.java
4  * *
5  * *********************************************************************** *
6  * *
7  * copyright : (C) 2007 by the members listed in the COPYING, *
8  * LICENSE and WARRANTY file. *
9  * email : info at matsim dot org *
10  * *
11  * *********************************************************************** *
12  * *
13  * This program is free software; you can redistribute it and/or modify *
14  * it under the terms of the GNU General Public License as published by *
15  * the Free Software Foundation; either version 2 of the License, or *
16  * (at your option) any later version. *
17  * See also COPYING, LICENSE and WARRANTY file *
18  * *
19  * *********************************************************************** */
20 
21 package org.matsim.core.router.util;
22 
23 import java.awt.geom.Rectangle2D;
24 import java.util.ArrayList;
25 import java.util.Collection;
26 import java.util.Iterator;
27 import java.util.Set;
28 import java.util.TreeMap;
29 import java.util.TreeSet;
30 
31 import org.apache.logging.log4j.LogManager;
32 import org.apache.logging.log4j.Logger;
33 import org.matsim.api.core.v01.Coord;
38 
39 /*package*/ class LandmarkerPieSlices {
40 
41  private Node[] landmarks;
42 
43  private Coord center = null;
44 
45  private final Rectangle2D.Double travelZone;
46 
47  private static final Logger log = LogManager.getLogger(LandmarkerPieSlices.class);
48 
49  private static final double ZONE_EXPANSION = 0.1;
50 
51  LandmarkerPieSlices(final int landmarkCount, final Rectangle2D.Double travelZone) {
52  this.landmarks = new Node[landmarkCount];
53  this.travelZone = travelZone;
54  }
55 
56  public void run(final Network network) {
57  Collection<? extends Node> nodes;
58  if (this.travelZone.getHeight() == 0 || this.travelZone.getWidth() == 0) {
59  nodes = network.getNodes().values();
60  } else {
61  nodes = getNodesInTravelZone(network);
62  }
63  run(nodes);
64  }
65 
66  private Set<Node> getNodesInTravelZone(final Network network) {
67  double minX = travelZone.getX();
68  double maxX = travelZone.getWidth() + minX;
69  double minY = travelZone.getY();
70  double maxY = travelZone.getHeight() + minY;
71 
72  // largen the zone...
73  maxX += (maxX - minX) * ZONE_EXPANSION;
74  minX -= (maxX - minX) * ZONE_EXPANSION;
75  maxY += (maxY - minY) * ZONE_EXPANSION;
76  minY -= (maxY - minY) * ZONE_EXPANSION;
77  Set<Node> resultNodes = new TreeSet<Node>();
78  for (Node n : network.getNodes().values()) {
79  if (n.getCoord().getX() <= maxX && n.getCoord().getX() >= minX
80  && n.getCoord().getY() <= maxY && n.getCoord().getY() >= minY) {
81  resultNodes.add(n);
82  }
83  }
84 
85  return resultNodes;
86  }
87 
88  public void run(final Collection<? extends Node> nodes) {
89  this.center = getCenter(nodes);
90  putLandmarks(nodes, this.landmarks.length);
91  }
92 
93  private void putLandmarks(final Collection<? extends Node> nodes, final int landmarkCount) {
94 
95  ArrayList<ArrayList<Node>> sectors = new ArrayList<ArrayList<Node>>();
96 
97  log.info("Filling sectors...");
98  double[][] angles = fillSectors(sectors, nodes);
99 
100  if (angles.length < landmarkCount) {
101  log.info("Reducing number of landmarks from " + landmarkCount + " to " + angles.length + "...");
102  this.landmarks = new Node[angles.length];
103  }
104  for (int i = 0; i < this.landmarks.length; i++) {
105  this.landmarks[i] = getLandmark(sectors.get(i), angles[i]);
106  }
107 
108  log.info("Refining landmarks...");
109  refineLandmarks(sectors, angles);
110  log.info("done");
111  }
112 
113  private double[][] fillSectors(final ArrayList<ArrayList<Node>> sectors, final Collection<? extends Node> nodes) {
114  ArrayList<double[]> angles = new ArrayList<double[]>();
115  // Sort nodes according to angle
116  TreeMap<Double, Node[]> sortedNodes = new TreeMap<Double, Node[]>();
117  Node[] nodeList;
118  for (Node node : nodes) {
119  double x = node.getCoord().getX() - this.center.getX();
120  double y = node.getCoord().getY() - this.center.getY();
121  double angle = Math.atan2(y, x) + Math.PI;
122  nodeList = sortedNodes.get(angle);
123  if (nodeList == null) {
124  nodeList = new Node[1];
125  nodeList[0] = node;
126  } else {
127  Node[] nodeList2 = new Node[nodeList.length + 1];
128  System.arraycopy(nodeList, 0, nodeList2, 0, nodeList.length);
129  nodeList2[nodeList.length] = node;
130  nodeList = nodeList2;
131  }
132  sortedNodes.put(angle, nodeList);
133  }
134  double lastAngle = 0;
135  Iterator<Node[]> it = sortedNodes.values().iterator();
136  if (it.hasNext()) {
137  // Fill sectors such that each sector contains on average the same number of nodes
138  Node[] tmpNodes = it.next();
139  int k = 0;
140  for (int i = 0; i < this.landmarks.length; i++) {
141 
142  sectors.add(new ArrayList<Node>());
143  Node node = null;
144  for (int j = 0; j < nodes.size() / this.landmarks.length; j++) {
145  if (k == tmpNodes.length) {
146  tmpNodes = it.next();
147  k = 0;
148  }
149  node = tmpNodes[k++];
150  sectors.get(angles.size()).add(node);
151  }
152  // Add the remaining nodes to the last sector
153  if (i == this.landmarks.length - 1) {
154  while (it.hasNext() || k < tmpNodes.length) {
155  if (k == tmpNodes.length) {
156  tmpNodes = it.next();
157  k = 0;
158  }
159  node = tmpNodes[k++];
160  sectors.get(angles.size()).add(node);
161  }
162  }
163  if (sectors.get(angles.size()).isEmpty()) {
164  log.info("There is no node in sector " + i + "!");
165  sectors.remove(angles.size());
166  } else {
167  // Get the angle of the "rightmost" node
168  double x = node.getCoord().getX() - this.center.getX();
169  double y = node.getCoord().getY() - this.center.getY();
170  double angle = Math.atan2(y, x) + Math.PI;
171  double[] tmp = new double[2];
172  tmp[0] = lastAngle;
173  tmp[1] = angle;
174  angles.add(tmp);
175  lastAngle = angle;
176  }
177  }
178  }
179  return angles.toArray(new double[0][2]);
180  }
181 
182  private Node getLandmark(final ArrayList<Node> nodes, final double[] angles) {
183  double maxDist = Double.NEGATIVE_INFINITY;
184  Node landmark = null;
185  for (Node node : nodes) {
186  if ((node.getOutLinks().size() > 1 && node.getInLinks().size() > 1)
187  || landmark == null) {
188  double x = node.getCoord().getX() - this.center.getX();
189  double y = node.getCoord().getY() - this.center.getY();
190  double angle = Math.atan2(y, x) + Math.PI;
191  double minAngelToBorder = 0;
192  if (angle - angles[0] < angles[1] - angle) {
193  minAngelToBorder = angle - angles[0];
194  } else {
195  minAngelToBorder = angles[1] - angle;
196  }
197  double distApprox = Math.sqrt(x * x + y * y) * (1 + minAngelToBorder / (2 * Math.PI));
198  // Set the node that is farthest away from the center to be the landmark in the current sector
199  if (distApprox > maxDist) {
200  landmark = node;
201  maxDist = distApprox;
202  }
203  }
204  }
205 
206  return landmark;
207  }
208 
209  private void refineLandmarks(final ArrayList<ArrayList<Node>> sectors, final double[][] sectorAngles) {
210  boolean doRefine = true;
211  double[] landmarkAngels = new double[this.landmarks.length];
212  for (int i = 0; i < this.landmarks.length; i++) {
213  double x = this.landmarks[i].getCoord().getX() - this.center.getX();
214  double y = this.landmarks[i].getCoord().getY() - this.center.getY();
215  landmarkAngels[i] = Math.atan2(y, x) + Math.PI;
216  }
217  double minAngelFactor = 0.5;
218  while (doRefine) {
219  doRefine = false;
220  for (int i = 0; i < this.landmarks.length && !sectors.get(i).isEmpty(); i++) {
221  int preInd = i - 1;
222  double angelDiff;
223  if (preInd == -1) {
224  preInd = this.landmarks.length - 1;
225  angelDiff = 2 * Math.PI + landmarkAngels[i] - landmarkAngels[preInd];
226  } else {
227  angelDiff = landmarkAngels[i] - landmarkAngels[preInd];
228  }
229  // Get the lower size angle of the two sectors
230  double minSectorSize = sectorAngles[i][1] - sectorAngles[i][0];
231  if (sectorAngles[preInd][1] - sectorAngles[preInd][0] < minSectorSize) {
232  minSectorSize = sectorAngles[preInd][1] - sectorAngles[preInd][0];
233  }
234  if (angelDiff < minSectorSize * minAngelFactor) {
235  // Change landmark that is nearer to the center
236  int indexToChange = 0;
237  if (CoordUtils.calcEuclideanDistance(this.center, this.landmarks[preInd].getCoord()) < CoordUtils.calcEuclideanDistance(this.center, this.landmarks[i].getCoord())) {
238  // Narrow the sector
239  sectorAngles[preInd][1] -= minSectorSize * minAngelFactor;
240  indexToChange = preInd;
241  } else {
242  // Narrow the sector
243  sectorAngles[i][0] += minSectorSize * minAngelFactor;
244  indexToChange = i;
245  }
246  removeNodesFromSector(sectors.get(indexToChange), sectorAngles[indexToChange]);
247  if (sectors.get(indexToChange).isEmpty()) {
248  log.info("There is no node in sector " + indexToChange + " after narrowing it!");
249  } else {
250  this.landmarks[indexToChange] = getLandmark(sectors.get(indexToChange), sectorAngles[indexToChange]);
251  }
252  double x = this.landmarks[indexToChange].getCoord().getX() - this.center.getX();
253  double y = this.landmarks[indexToChange].getCoord().getY() - this.center.getY();
254  landmarkAngels[indexToChange] = Math.atan2(y, x) + Math.PI;
255  doRefine = true;
256  break;
257  }
258  }
259  }
260  }
261 
262  private void removeNodesFromSector(final ArrayList<Node> sector,
263  final double[] sectorAngles) {
264  int i = 0;
265  while (i < sector.size()) {
266  Node node = sector.get(i);
267  double x = node.getCoord().getX() - this.center.getX();
268  double y = node.getCoord().getY() - this.center.getY();
269  double angle = Math.atan2(y, x) + Math.PI;
270  if (angle < sectorAngles[0] || angle > sectorAngles[1]) {
271  sector.remove(i);
272  } else {
273  i++;
274  }
275  }
276  }
277 
278  private Coord getCenter(final Collection<? extends Node> nodes) {
279  double[] bBox = NetworkUtils.getBoundingBox(nodes);
280  double minX = bBox[0];
281  double minY = bBox[1];
282  double maxX = bBox[2];
283  double maxY = bBox[3];
284 
285  double centerX = (maxX - minX) / 2 + minX;
286  double centerY = (maxY - minY) / 2 + minY;
287 
288  return new Coord(centerX, centerY);
289  }
290 
291  public Node[] getLandmarks() {
292  return this.landmarks.clone();
293  }
294 
295 }