001// License: Apache 2.0. For details, see LICENSE file. 002package org.openstreetmap.josm.plugins.wkt; 003 004import org.openstreetmap.josm.data.coor.LatLon; 005import org.openstreetmap.josm.data.osm.DataSet; 006import org.openstreetmap.josm.data.osm.Node; 007import org.openstreetmap.josm.data.osm.OsmPrimitive; 008import org.openstreetmap.josm.data.osm.Relation; 009import org.openstreetmap.josm.data.osm.RelationMember; 010import org.openstreetmap.josm.data.osm.Way; 011import org.openstreetmap.josm.gui.progress.NullProgressMonitor; 012import org.openstreetmap.josm.gui.progress.ProgressMonitor; 013import org.openstreetmap.josm.io.AbstractReader; 014import org.openstreetmap.josm.io.IllegalDataException; 015import org.openstreetmap.josm.tools.Logging; 016 017import java.io.InputStream; 018import java.io.InputStreamReader; 019import java.util.ArrayList; 020import java.util.List; 021import java.util.Map; 022import java.util.Optional; 023import java.util.Scanner; 024import java.util.TreeMap; 025import java.util.stream.Collectors; 026 027import static org.openstreetmap.josm.tools.I18n.tr; 028 029/** 030 * Reader that reads WKT files. 031 * Modified version of geojson reader. Thanks to the geojson authors. 032 */ 033public class WktReader extends AbstractReader { 034 035 WktReader() { 036 // Restricts visibility 037 } 038 039 private void parse(String wkt) { 040 String type = wkt.substring(0, wkt.indexOf('(')).toUpperCase().trim(); 041 String coordinates = wkt.substring(wkt.indexOf('(')+1,wkt.lastIndexOf(')')); 042 043 switch (type) { 044 case "POINT": 045 parsePoint(coordinates); 046 break; 047 case "MULTIPOINT": 048 parseMultiPoint(coordinates); 049 break; 050 case "LINESTRING": 051 parseLineString(coordinates); 052 break; 053 case "MULTILINESTRING": 054 parseMultiLineString(coordinates); 055 break; 056 case "POLYGON": 057 parsePolygon(coordinates); 058 break; 059 case "MULTIPOLYGON": 060 parseMultiPolygon(coordinates); 061 break; 062 case "GEOMETRYCOLLECTION": 063 parseGeometryCollection(coordinates); 064 break; 065 default: 066 parseUnknown(wkt); 067 } 068 } 069 070 private void parseGeometryCollection(String geometries) { 071 while (geometries.length() > 0) 072 { 073 int ind = getElementFromCollection(geometries); 074 String geometry = geometries.substring(0,ind); 075 while (ind<geometries.length() && !Character.isLetter(geometries.charAt(ind))) 076 { 077 ind++; 078 } 079 geometries = geometries.substring(ind); 080 parse(geometry); 081 } 082 } 083 084 private void parsePoint(final String coordinates) { 085 String[] coord = coordinates.split(" "); 086 double lat = Double.parseDouble(coord[1]); 087 double lon = Double.parseDouble(coord[0]); 088 Node node = createNode(lat, lon); 089 } 090 091 private void parseMultiPoint(final String coordinates) { 092 String[] points = coordinates.split(","); 093 for (String point : points) { 094 parsePoint(removeBrackets(point)); 095 } 096 } 097 098 private void parseLineString(final String coordinates) { 099 String[] points = coordinates.split(","); 100 if (points.length == 0) { 101 return; 102 } 103 createWay(coordinates, false); 104 } 105 106 private void parseMultiLineString(final String coordinates) { 107 String[] lines = coordinates.split("\\),"); 108 for (String line : lines) { 109 parseLineString(removeBrackets(line)); 110 } 111 } 112 113 private void parsePolygon(final String coordinates) { 114 String[] parts = coordinates.split("\\),"); 115 116 final Relation multipolygon = new Relation(); 117 multipolygon.put("type", "multipolygon"); 118 createWay(removeBrackets(parts[0]), true) 119 .ifPresent(way -> multipolygon.addMember(new RelationMember("outer", way))); 120 121 for (int i=1; i<parts.length; i++) { 122 createWay(removeBrackets(parts[1]), true) 123 .ifPresent(way -> multipolygon.addMember(new RelationMember("inner", way))); 124 } 125 getDataSet().addPrimitive(multipolygon); 126 } 127 128 private void parsePolygon(final String coordinates, final Relation multipolygon) { 129 String[] parts = coordinates.split("\\),"); 130 131 multipolygon.put("type", "multipolygon"); 132 createWay(removeBrackets(parts[0]), true) 133 .ifPresent(way -> multipolygon.addMember(new RelationMember("outer", way))); 134 135 for (int i=1; i<parts.length; i++) { 136 createWay(removeBrackets(parts[1]), true) 137 .ifPresent(way -> multipolygon.addMember(new RelationMember("inner", way))); 138 } 139 } 140 141 private void parseMultiPolygon(final String coordinates) { 142 String[] polygons = coordinates.split("\\)\\),"); 143 final Relation multipolygon = new Relation(); 144 for (String polygon : polygons) { 145 parsePolygon(polygon, multipolygon); 146 } 147 getDataSet().addPrimitive(multipolygon); 148 } 149 150 private Node createNode(final double lat, final double lon) { 151 final Node node = new Node(new LatLon(lat, lon)); 152 getDataSet().addPrimitive(node); 153 return node; 154 } 155 156 private Optional<Way> createWay(final String coordinates, final boolean autoClose) { 157 String[] points = coordinates.split(","); 158 if (points.length == 0) { 159 return Optional.empty(); 160 } 161 162 List<LatLon> latlons = new ArrayList<LatLon>(); 163 for (String point : points) 164 { 165 String[] coord = point.trim().split(" "); 166 double lat = Double.parseDouble(coord[1]); 167 double lon = Double.parseDouble(coord[0]); 168 latlons.add(new LatLon(lat,lon)); 169 } 170 171 final int size = latlons.size(); 172 final boolean doAutoclose; 173 if (size > 1) { 174 if (latlons.get(0).equals(latlons.get(size - 1))) { 175 // Remove last coordinate, but later add first node to the end 176 latlons.remove(size - 1); 177 doAutoclose = true; 178 } else { 179 doAutoclose = autoClose; 180 } 181 } else { 182 doAutoclose = false; 183 } 184 185 final Way way = new Way(); 186 way.setNodes(latlons.stream().map(Node::new).collect(Collectors.toList())); 187 if (doAutoclose) { 188 way.addNode(way.getNode(0)); 189 } 190 191 way.getNodes().stream().distinct().forEach(it -> getDataSet().addPrimitive(it)); 192 getDataSet().addPrimitive(way); 193 194 return Optional.of(way); 195 } 196 197 private String removeBrackets(final String s) 198 { 199 int start = 0; 200 for (int i = 0; i<s.length(); i++) 201 { 202 if (s.charAt(i) == '(') start = i+1; 203 else if (s.charAt(i) == ')') return s.substring(start,i).trim(); 204 } 205 return s.substring(start,s.length()).trim(); 206 } 207 208 private int getElementFromCollection(final String s) 209 { 210 int ind = s.indexOf('(') + 1; 211 if (ind == 0) return -1; 212 int level = 1; 213 while (ind < s.length() && level > 0) 214 { 215 if (s.charAt(ind) == '(') level++; 216 else if (s.charAt(ind) == ')') level--; 217 ind++; 218 } 219 return ind; 220 } 221 222 private void parseUnknown(final String object) { 223 Logging.warn(tr("Unknown wkt object found {0}", object)); 224 } 225 226 @Override 227 protected DataSet doParseDataSet(InputStream source, ProgressMonitor progressMonitor) throws IllegalDataException { 228 229 Scanner s = new Scanner(source).useDelimiter("\\A"); 230 String text = s.hasNext() ? s.next() : ""; 231 parse(text); 232 233 return getDataSet(); 234 } 235 236 /** 237 * Parse the given input source and return the dataset. 238 * 239 * @param source the source input stream. Must not be null. 240 * @param progressMonitor the progress monitor. If null, {@link NullProgressMonitor#INSTANCE} is assumed 241 * @return the dataset with the parsed data 242 * @throws IllegalDataException if an error was found while parsing the data from the source 243 * @throws IllegalArgumentException if source is null 244 */ 245 public static DataSet parseDataSet(InputStream source, ProgressMonitor progressMonitor) throws IllegalDataException { 246 return new WktReader().doParseDataSet(source, progressMonitor); 247 } 248 249}