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 nodes = new ArrayList(); /** * 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.

* k="natural" - v="water" | v="reservoir" | v="wetland" | v="wood" */ public EOSMNatural natural; /** * Tag ten oznacza rzekę lub ciek wodny opisywany liniowo. *

Wyjątek: waterway=riverbank, który opisuje obszar. *

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.

* k="water" - v="lake" | v="reservoir" | v="river" | v="canal" | v="cove" | v="lagoon" | v="pond" */ public EOSMWater water; /** * Tag oznacza obiekt obszarowy.

* 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.

* 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 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 newList = new ArrayList(); 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 newList = new ArrayList(); 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 + '}'; } }