Files
terrain-utilities/src/main/java/pl/wat/ms4ds/terrain/konwersja/Way.java

605 lines
22 KiB
Java

package pl.wat.ms4ds.terrain.konwersja;
import pl.wat.ms4ds.common.EGeoDirection;
import pl.wat.ms4ds.terrain.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
/**
* Bazowa klasa dla obiektów OpenStreetMap.
*/
public class Way {
private static final Logger logger = LoggerFactory.getLogger(Way.class);
public String id;
ArrayList<Node> nodes = new ArrayList<Node>();
/**
* k="highway" - v="motorway" | v="trunk" | v="primary" | v="secondary" | v="tertiary" | v="unclassified"
* | v="residential" | v="pedestrian" | v="track"
* | v="motorway_link" | v="trunk_link" | v="primary_link" | v="secondary_link" | v="tertiary_link"
*/
public EOSMHighway highway;
/**
* Tag oznacza obiekt obszarowy. <p>
* k="natural" - v="water" | v="reservoir" | v="wetland" | v="wood"
*/
public EOSMNatural natural;
/**
* Tag ten oznacza rzekę lub ciek wodny opisywany liniowo.
* <p> Wyjątek: waterway=riverbank, który opisuje obszar.
* <p> k="waterway" - v="river" | v="riverbank" | v="stream" | v="canal" | v="drain" | v="ditch"
*/
public EOSMWaterway waterway;
/**
* Tag stosowany w połączeniu z tagiem obszarowym: natural=water. <p>
* k="water" - v="lake" | v="reservoir" | v="river" | v="canal" | v="cove" | v="lagoon" | v="pond"
*/
public EOSMWater water;
/**
* Tag oznacza obiekt obszarowy. <p>
* k="landuse" - v="forest" | v="residential" | v="commercial"
*/
public EOSMLanduse landuse;
/**
* k="amenity" - v="hospital" | v="school" | v="post_office" | v="police" | v="marketplace" | v="fire_station" |
* v="theatre" | v="cinema" | v="pharmacy" | v="nursing_home" | v="bank" | v="fuel"
* v="university" | v="library"
*/
public EOSMAmenity amenity;
/**
* k="building" - v="yes" | v="chapel" | v="church" | v="house" | v="office" | v="manufacture" | v="industrial"
*/
public EOSMBuilding building;
/**
* k="bridge" - v="yes" | "viaduct"
*/
public EOSMBridge bridge;
/**
* Tag oznacza obiekt obszarowy. <p>
* k="landcover" - v="trees" | v="water" | v="grass"
*/
public EOSMLandcover landcover;
public Way(String id) {
this.id = id;
}
public Way(Way oryg) {
this.id = oryg.id;
}
public void copyTags(Way oryg) {
this.highway = oryg.highway;
this.natural = oryg.natural;
this.waterway = oryg.waterway;
this.water = oryg.water;
this.landuse = oryg.landuse;
this.amenity = oryg.amenity;
this.building = oryg.building;
this.bridge = oryg.bridge;
this.landcover = oryg.landcover;
}
public void copyTags(Relation oryg) {
this.natural = oryg.natural;
this.waterway = oryg.waterway;
this.water = oryg.water;
this.landuse = oryg.landuse;
this.landcover = oryg.landcover;
}
public boolean tagged() {
if (landuse != null || natural != null || landcover != null
|| highway != null || waterway != null
|| building != null || amenity != null || bridge != null) {
return true;
}
return false;
}
void removeSimilarNodes() {
if (nodes.size() == 0) {
return;
}
Node node_i;
Node node_j;
Node similarToNode[] = new Node[nodes.size()];
boolean similarFound = false;
for (int i = 0; i < nodes.size() - 1; i++) {
node_i = nodes.get(i);
if (similarToNode[i] != null) {
continue;
}
for (int j = i + 1; j < nodes.size(); j++) {
node_j = nodes.get(j);
if (node_i.idX == node_j.idX && node_i.idY == node_j.idY) {
similarToNode[j] = node_i;
node_i.buildingsCount += node_j.buildingsCount;
similarFound = true;
}
}
}
if (similarFound) {
ArrayList<Node> newList = new ArrayList<>();
for (int i = 0; i < nodes.size(); i++) {
node_i = nodes.get(i);
if (similarToNode[i] == null) {
newList.add(node_i);
}
}
nodes = newList;
removeSimilarNodes();
}
}
void removeCollinearNodes(boolean isPolygon) {
if (nodes.size() < 3) {
return;
}
boolean toDelete[] = new boolean[nodes.size()];
Node node_i;
Node node_i_next;
Node node_i_prev;
boolean collinearFound = false;
int dlMax = nodes.size();
int iStart = 0;
if (!isPolygon) {
// łamana otwarta
dlMax = nodes.size() - 1;
iStart = 1;
}
for (int i = iStart; i < dlMax; i++) {
int i_plus_1 = (i + 1) % nodes.size();
int i_minus_1 = (i + nodes.size() - 1) % nodes.size();
node_i = nodes.get(i);
node_i_next = nodes.get(i_plus_1);
node_i_prev = nodes.get(i_minus_1);
if (GeomUtils.include(node_i_prev.idX, node_i_prev.idY, node_i_next.idX, node_i_next.idY,
node_i.idX, node_i.idY)) {
// i-ty do usuniecia
toDelete[i] = true;
collinearFound = true;
} else if (GeomUtils.include(node_i.idX, node_i.idY, node_i_next.idX, node_i_next.idY,
node_i_prev.idX, node_i_prev.idY)) {
// i-1-ty do usuniecia
toDelete[i_minus_1] = true;
collinearFound = true;
} else if (GeomUtils.include(node_i_prev.idX, node_i_prev.idY, node_i.idX, node_i.idY,
node_i_next.idX, node_i_next.idY)) {
// i+1-ty do usuniecia
toDelete[i_plus_1] = true;
collinearFound = true;
}
}
if (collinearFound) {
ArrayList<Node> newList = new ArrayList<Node>();
for (int i = 0; i < nodes.size(); i++) {
node_i = nodes.get(i);
if (!toDelete[i]) {
newList.add(node_i);
}
}
// logger.trace("Liczba oryg. wezlow= {}, liczba niewspolliniowych wezlow= {}, roznica= {}", nodes.size(), newList.size(), nodes.size() - newList.size());
nodes = newList;
removeCollinearNodes(isPolygon);
}
}
void removeSteps() {
if (nodes.size() < 3) {
return;
}
boolean toDelete[] = new boolean[nodes.size()];
Node node_i;
Node node_j;
Node node_k;
boolean bylSchodek = false;
for (int i = 0; i < nodes.size() - 2; i++) {
node_i = nodes.get(i);
node_j = nodes.get(i + 1);
node_k = nodes.get(i + 2);
int absX_i_j = Math.abs(node_j.idX - node_i.idX);
int absY_i_j = Math.abs(node_j.idY - node_i.idY);
int absX_j_k = Math.abs(node_k.idX - node_j.idX);
int absY_j_k = Math.abs(node_k.idY - node_j.idY);
if (absX_i_j + absY_i_j + absX_j_k + absY_j_k == 2) {
// wezly moga tworzyc schodek
if (absX_i_j + absX_j_k == 1) {
// wezly tworza schodek, zatem srodkowy wezel schodka do usuniecia
toDelete[i + 1] = true;
bylSchodek = true;
// przeskok o usuwany wezel
i++;
}
}
}
if (bylSchodek) {
ArrayList<Node> newList = new ArrayList<Node>();
for (int i = 0; i < nodes.size(); i++) {
if (!toDelete[i]) {
newList.add(nodes.get(i));
}
}
if (nodes.size() > newList.size()) {
// logger.trace("Liczba oryg. wezlow= {}, liczba roznych wezlow= {}, roznica= {}", nodes.size(), newList.size(), nodes.size() - newList.size());
}
nodes = newList;
removeSteps();
}
}
void writeLinearFeatureIntoSquares(ELinearFeature type) {
if (nodes.size() == 0) {
return;
}
Coord.Grid[] punktyLamanej = new Coord.Grid[nodes.size()];
for (int i = 0; i < nodes.size(); i++) {
punktyLamanej[i] = new Coord.Grid(nodes.get(i).idX, nodes.get(i).idY);
}
Square kw0;
Square kw1;
Coord.Grid id0;
Coord.Grid id1;
EGeoDirection kier;
Coord.Grid[] kwadraty = GeomUtils.kwadratyLamanej2(punktyLamanej);
// float dlug = GeomUtils.dlugoscDrogiPoKwadratch(kwadraty);
for (int i = 0; i < kwadraty.length - 1; i++) {
try {
id0 = kwadraty[i];
kw0 = Teren.getSquare(id0.x, id0.y);
id1 = kwadraty[i + 1];
kw1 = Teren.getSquare(id1.x, id1.y);
kier = GeomUtils.kierunekDlaSasiada(id0, id1);
switch (type) {
case ROAD:
kw0.jestDroga[kier.id] = true;
kw0.jestDroga[kier.oppositeDirect().id] = true;
break;
case WATER_WAY:
kw0.jestPrzeszkodaWodna[kier.id] = true;
kw0.jestPrzeszkodaWodna[kier.oppositeDirect().id] = true;
break;
case DITCH:
kw0.jestRow[kier.id] = true;
kw0.jestRow[kier.oppositeDirect().id] = true;
break;
default:
}
} catch (Exception e) {
logger.warn(e.toString());
}
}
}
public static void writeAreaFeatureIntoSquares2(EAreaFeature type, boolean clearFeature, Coord.Grid[] polygon) {
if (polygon.length > 20) {
// podział wielokata na dwa mniejsze
int m = 2;
Coord.Grid pocz = polygon[0];
boolean poprawnyPodzial = false;
while (m < polygon.length - 1 && !poprawnyPodzial) {
// sprawdzenie, czy punkty wielokata po podziale sa po jednej stronie wektora podzialu
poprawnyPodzial = true;
Coord.Grid kon = polygon[m];
for (int i = 0; i < polygon.length; i++) {
int i_puls_1 = (i + 1) % polygon.length;
boolean przeciecie = GeomUtils.intersection(pocz, kon, polygon[i], polygon[i_puls_1]);
if (przeciecie) {
// sprawdzenie, czy jakiś koniec jednego odcinka jest równy końcowi drugiego odcinka
boolean b = pocz.equals(polygon[i]) ||
pocz.equals(polygon[i_puls_1]) ||
kon.equals(polygon[i]) ||
kon.equals(polygon[i_puls_1]);
if (!b) {
poprawnyPodzial = false;
m++;
break;
}
}
}
}
if (poprawnyPodzial) {
// punkt podziału wielokąta jest poprawny, zatem dzielę wielokąt na dwa
Coord.Grid[] polygon1 = new Coord.Grid[m + 1];
for (int i = 0; i < polygon1.length; i++) {
polygon1[i] = polygon[i];
}
writeAreaFeatureIntoSquares2(type, clearFeature, polygon1);
Coord.Grid[] polygon2 = new Coord.Grid[polygon.length - m + 1];
polygon2[0] = polygon[0];
for (int i = m; i < polygon.length; i++) {
polygon2[i - m + 1] = polygon[i];
}
writeAreaFeatureIntoSquares2(type, clearFeature, polygon2);
} else {
// nie udało się poprawnie podzielić wielokąta, zatem przesuwam wierzchołki, aby zmienić wierzchołek
// startowy, który jest wierchołkiem referencyjnym podziału (drugi wierzchołek podziału jest szukany)
Coord.Grid temp = polygon[0];
for (int i = 0; i < polygon.length - 1; i++) {
polygon[i] = polygon[i + 1];
}
polygon[polygon.length - 1] = temp;
writeAreaFeatureIntoSquares2(type, clearFeature, polygon);
}
return;
}
float val = (clearFeature) ? 0.0f : 1.0f;
int minX = polygon[0].x;
int minY = polygon[0].y;
int maxX = polygon[0].x;
int maxY = polygon[0].y;
for (int i = 1; i < polygon.length; i++) {
minX = Math.min(polygon[i].x, minX);
minY = Math.min(polygon[i].y, minY);
maxX = Math.max(polygon[i].x, maxX);
maxY = Math.max(polygon[i].y, maxY);
}
Coord.Grid idTest = new Coord.Grid();
boolean inside;
for (int j = maxY; j >= minY; j--) {
for (int i = minX; i <= maxX; i++) {
idTest.x = i;
idTest.y = j;
Square kw = Teren.getSquare(idTest.x, idTest.y);
if (kw == Square.EMPTY) {
continue;
}
inside = GeomUtils.insidePolygon(polygon, idTest);
if (inside) {
switch (type) {
case FOREST:
kw.stopienZalesienia = val;
break;
case WATER:
kw.stopienZawodnienia = val;
break;
case SWAMP:
kw.stopienZabagnienia = val;
break;
case BUILDINGS:
kw.stopienZabudowy = val;
break;
default:
}
}
}
}
}
public static void writeAreaFeatureIntoSquares(EAreaFeature type, boolean clearFeature, Coord.Grid[] polygon,
int minX, int maxX, int minY, int maxY) {
float val = (clearFeature) ? 0.0f : 1.0f;
Coord.Grid idTest = new Coord.Grid();
boolean inside;
for (int i = minX; i <= maxX; i++) {
for (int j = minY; j <= maxY; j++) {
idTest.x = i;
idTest.y = j;
Square kw = Teren.getSquare(idTest.x, idTest.y);
if (kw == Square.EMPTY) {
continue;
}
inside = GeomUtils.insidePolygon(polygon, idTest);
if (inside) {
switch (type) {
case FOREST:
kw.stopienZalesienia = val;
break;
case WATER:
kw.stopienZawodnienia = val;
break;
case SWAMP:
kw.stopienZabagnienia = val;
break;
case BUILDINGS:
kw.stopienZabudowy = val;
break;
default:
}
}
}
}
}
void writeAreaFeatureIntoSquares2(EAreaFeature type, boolean clearFeature) {
if (nodes.size() == 0) {
return;
}
Square kw;
float val = (clearFeature) ? 0.0f : 1.0f;
if (nodes.size() == 1) {
kw = Teren.getSquare(nodes.get(0).idX, nodes.get(0).idY);
switch (type) {
case FOREST:
kw.stopienZalesienia = val;
break;
case WATER:
kw.stopienZawodnienia = val;
break;
case SWAMP:
kw.stopienZabagnienia = val;
break;
case BUILDINGS:
kw.stopienZabudowy = val;
break;
default:
}
return;
}
if (nodes.size() == 2) {
Coord.Grid[] kwadraty = GeomUtils.kwadratyOdcinka(nodes.get(0).idX, nodes.get(0).idY,
nodes.get(1).idX, nodes.get(1).idY);
for (int i = 0; i < kwadraty.length; i++) {
kw = Teren.getSquare(kwadraty[i].x, kwadraty[i].y);
switch (type) {
case FOREST:
kw.stopienZalesienia = val;
break;
case WATER:
kw.stopienZawodnienia = val;
break;
case SWAMP:
kw.stopienZabagnienia = val;
break;
case BUILDINGS:
kw.stopienZabudowy = val;
break;
default:
}
}
return;
}
Coord.Grid[] wielokat = new Coord.Grid[nodes.size()];
for (int i = 0; i < nodes.size(); i++) {
wielokat[i] = new Coord.Grid(nodes.get(i).idX, nodes.get(i).idY);
}
writeAreaFeatureIntoSquares2(type, clearFeature, wielokat);
}
void writeAreaFeatureIntoSquares(EAreaFeature type, boolean clearFeature) {
if (nodes.size() == 0) {
return;
}
Square kw;
float val = (clearFeature) ? 0.0f : 1.0f;
if (nodes.size() == 1) {
kw = Teren.getSquare(nodes.get(0).idX, nodes.get(0).idY);
switch (type) {
case FOREST:
kw.stopienZalesienia = val;
break;
case WATER:
kw.stopienZawodnienia = val;
break;
case SWAMP:
kw.stopienZabagnienia = val;
break;
case BUILDINGS:
kw.stopienZabudowy = val;
break;
default:
}
return;
}
if (nodes.size() == 2) {
Coord.Grid[] kwadraty = GeomUtils.kwadratyOdcinka(nodes.get(0).idX, nodes.get(0).idY,
nodes.get(1).idX, nodes.get(1).idY);
for (int i = 0; i < kwadraty.length; i++) {
kw = Teren.getSquare(kwadraty[i].x, kwadraty[i].y);
switch (type) {
case FOREST:
kw.stopienZalesienia = val;
break;
case WATER:
kw.stopienZawodnienia = val;
break;
case SWAMP:
kw.stopienZabagnienia = val;
break;
case BUILDINGS:
kw.stopienZabudowy = val;
break;
default:
}
}
return;
}
Coord.Grid[] wielokat = new Coord.Grid[nodes.size()];
int minX = nodes.get(0).idX;
int minY = nodes.get(0).idY;
int maxX = nodes.get(0).idX;
int maxY = nodes.get(0).idY;
for (int i = 0; i < nodes.size(); i++) {
wielokat[i] = new Coord.Grid(nodes.get(i).idX, nodes.get(i).idY);
minX = Math.min(wielokat[i].x, minX);
minY = Math.min(wielokat[i].y, minY);
maxX = Math.max(wielokat[i].x, maxX);
maxY = Math.max(wielokat[i].y, maxY);
}
int ileKwTest = (maxX - minX) * (maxY - minY);
if (ileKwTest > 100000) {
int dx = maxX - minX;
int dy = maxY - minY;
Worker[] workers = new Worker[4];
workers[0] = new Worker(type, clearFeature, wielokat, minX, minX + dx / 2, minY, minY + dy / 2);
workers[1] = new Worker(type, clearFeature, wielokat, minX + dx / 2 + 1, maxX, minY, minY + dy / 2);
workers[2] = new Worker(type, clearFeature, wielokat, minX, minX + dx / 2, minY + dy / 2 + 1, maxY);
workers[3] = new Worker(type, clearFeature, wielokat, minX + dx / 2 + 1, maxX, minY + dy / 2 + 1, maxY);
for (int i = 0; i < workers.length; i++) {
workers[i].start();
}
return;
}
Coord.Grid idTest = new Coord.Grid();
boolean nalezyDoWielokata;
// int liczKw = 0;
// int liczKwObszaru = 0;
// for (int j = maxY; j >= minY; j--) {
// for (int i = minX; i <= maxX; i++) {
for (int i = minX; i <= maxX; i++) {
for (int j = minY; j <= maxY; j++) {
idTest.x = i;
idTest.y = j;
// char c = ' ';
// liczKw++;
kw = Teren.getSquare(idTest.x, idTest.y);
if (kw == Square.EMPTY) {
continue;
}
nalezyDoWielokata = GeomUtils.insidePolygon(wielokat, idTest);
if (nalezyDoWielokata) {
// c = 'O';
switch (type) {
case FOREST:
kw.stopienZalesienia = val;
break;
case WATER:
kw.stopienZawodnienia = val;
break;
case SWAMP:
kw.stopienZabagnienia = val;
break;
case BUILDINGS:
kw.stopienZabudowy = val;
break;
default:
}
// liczKwObszaru++;
}
// rysowanie wielokata
// for (int k = 0; k < wielokat.length; k++) {
// if (wielokat[k].equals(idTest)) {
// c = 'X';
// break;
// }
// }
// System.out.print(c);
// System.out.print(' ');
}
// System.out.println();
}
}
@Override
public String toString() {
return "Way{id=" + id +
", len=" + nodes.size() +
", nodes=" + nodes +
'}';
}
}