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}