Class GeometryStrategies

  1 /*
  2  * Copyright (c) 2016 Vivid Solutions.
  3  *
  4  * All rights reserved. This program and the accompanying materials
  5  * are made available under the terms of the Eclipse Public License 2.0
  6  * and Eclipse Distribution License v. 1.0 which accompanies this distribution.
  7  * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v20.html
  8  * and the Eclipse Distribution License is available at
  9  *
 10  * http://www.eclipse.org/org/documents/edl-v10.php.
 11  */
 12 package org.locationtech.jts.io.gml2;
 13  
 14 import java.util.HashMap;
 15 import java.util.List;
 16 import java.util.WeakHashMap;
 17 import java.util.regex.Matcher;
 18 import java.util.regex.Pattern;
 19  
 20 import org.locationtech.jts.geom.Coordinate;
 21 import org.locationtech.jts.geom.CoordinateSequence;
 22 import org.locationtech.jts.geom.Envelope;
 23 import org.locationtech.jts.geom.Geometry;
 24 import org.locationtech.jts.geom.GeometryCollection;
 25 import org.locationtech.jts.geom.GeometryFactory;
 26 import org.locationtech.jts.geom.LineString;
 27 import org.locationtech.jts.geom.LinearRing;
 28 import org.locationtech.jts.geom.MultiLineString;
 29 import org.locationtech.jts.geom.MultiPoint;
 30 import org.locationtech.jts.geom.MultiPolygon;
 31 import org.locationtech.jts.geom.Point;
 32 import org.locationtech.jts.geom.Polygon;
 33 import org.locationtech.jts.io.gml2.GMLHandler.Handler;
 34 import org.locationtech.jts.util.StringUtil;
 35 import org.xml.sax.Attributes;
 36 import org.xml.sax.SAXException;
 37  
 38  
 39 /**
 40  * Container for GML2 Geometry parsing strategies which can be represented in JTS.
 41  *
 42  * @author David Zwiers, Vivid Solutions.
 43  */
 44 public class GeometryStrategies{
 45  
 46     /**
 47      * This set of strategies is not expected to be used directly outside of this distribution.
 48      * 
 49      * The implementation of this class are intended to be used as static function points in C. These strategies should be associated with an element when the element begins. The strategy is utilized at the end of the element to create an object of value to the user. 
 50      * 
 51      * In this case all the objects are either java.lang.* or JTS Geometry objects
 52      *
 53      * @author David Zwiers, Vivid Solutions.
 54      */
 55     static interface ParseStrategy{
 56         /**
 57          * @param arg Value to interpret
 58          * @param gf GeometryFactory
 59          * @return The interpreted value
 60          * @throws SAXException 
 61          */
 62         Object parse(Handler arg, GeometryFactory gf) throws SAXException;
 63     }
 64     
 65     private static HashMap strategies = loadStrategies();
 66     private static HashMap loadStrategies(){
 67         HashMap strats = new HashMap();
 68         
 69         // point
 70         strats.put(GMLConstants.GML_POINT.toLowerCase(),new ParseStrategy(){
 71  
 72             public Object parse(Handler arg, GeometryFactory gf) throws SAXException {
 73                 // one child, either a coord
 74                 // or a coordinate sequence
 75                 
 76                 if(arg.children.size()!=1)
 77                     throw new SAXException("Cannot create a point without exactly one coordinate");
 78  
 79                 int srid = getSrid(arg.attrs,gf.getSRID());
 80  
 81                 Object c = arg.children.get(0);
 82                 Point p = null;
 83                 if(c instanceof Coordinate){
 84                     p = gf.createPoint((Coordinate)c);
 85                 }else{
 86                     p = gf.createPoint((CoordinateSequence)c);
 87                 }
 88                 if(p.getSRID()!=srid)
 89                     p.setSRID(srid);
 90                 
 91                 return p;
 92             }
 93         });
 94         
 95         // linestring
 96         strats.put(GMLConstants.GML_LINESTRING.toLowerCase(),new ParseStrategy(){
 97  
 98             public Object parse(Handler arg, GeometryFactory gf) throws SAXException {
 99                 // one child, either a coord
100                 // or a coordinate sequence
101                 
102                 if(arg.children.size()<1)
103                     throw new SAXException("Cannot create a linestring without atleast two coordinates or one coordinate sequence");
104  
105                 int srid = getSrid(arg.attrs,gf.getSRID());
106                 
107                 LineString ls = null;
108                 if(arg.children.size() == 1){
109                     // coord set
110                     try{
111                         CoordinateSequence cs = (CoordinateSequence) arg.children.get(0);
112                         ls = gf.createLineString(cs);
113                     }catch(ClassCastException e){
114                         throw new SAXException("Cannot create a linestring without atleast two coordinates or one coordinate sequence",e);
115                     }
116                 }else{
117                     try{
118                         Coordinate[] coords = (Coordinate[]) arg.children.toArray(new Coordinate[arg.children.size()]);
119                         ls = gf.createLineString(coords);
120                     }catch(ClassCastException e){
121                         throw new SAXException("Cannot create a linestring without atleast two coordinates or one coordinate sequence",e);
122                     }
123                 }
124                 
125                 if(ls.getSRID()!=srid)
126                     ls.setSRID(srid);
127                 
128                 return ls;
129             }
130         });
131         
132         // linearring
133         strats.put(GMLConstants.GML_LINEARRING.toLowerCase(),new ParseStrategy(){
134  
135             public Object parse(Handler arg, GeometryFactory gf) throws SAXException {
136                 // one child, either a coord
137                 // or a coordinate sequence
138                 
139                 if(arg.children.size()!=1 && arg.children.size()<4)
140                     throw new SAXException("Cannot create a linear ring without atleast four coordinates or one coordinate sequence");
141  
142                 int srid = getSrid(arg.attrs,gf.getSRID());
143                 
144                 LinearRing ls = null;
145                 if(arg.children.size() == 1){
146                     // coord set
147                     try{
148                         CoordinateSequence cs = (CoordinateSequence) arg.children.get(0);
149                         ls = gf.createLinearRing(cs);
150                     }catch(ClassCastException e){
151                         throw new SAXException("Cannot create a linear ring without atleast four coordinates or one coordinate sequence",e);
152                     }
153                 }else{
154                     try{
155                         Coordinate[] coords = (Coordinate[]) arg.children.toArray(new Coordinate[arg.children.size()]);
156                         ls = gf.createLinearRing(coords);
157                     }catch(ClassCastException e){
158                         throw new SAXException("Cannot create a linear ring without atleast four coordinates or one coordinate sequence",e);
159                     }
160                 }
161                 
162                 if(ls.getSRID()!=srid)
163                     ls.setSRID(srid);
164                 
165                 return ls;
166             }
167         });
168         
169         // polygon
170         strats.put(GMLConstants.GML_POLYGON.toLowerCase(),new ParseStrategy(){
171  
172             public Object parse(Handler arg, GeometryFactory gf) throws SAXException {
173                 // one child, either a coord
174                 // or a coordinate sequence
175                 
176                 if(arg.children.size()<1)
177                     throw new SAXException("Cannot create a polygon without atleast one linear ring");
178  
179                 int srid = getSrid(arg.attrs,gf.getSRID());
180                 
181                 LinearRing outer = (LinearRing) arg.children.get(0); // will be the first
182                 List t = arg.children.size()>1?arg.children.subList(1,arg.children.size()):null;
183                 LinearRing[] inner = t==null?null:(LinearRing[]) t.toArray(new LinearRing[t.size()]);
184                 
185                 Polygon p = gf.createPolygon(outer,inner);
186                 
187                 if(p.getSRID()!=srid)
188                     p.setSRID(srid);
189                 
190                 return p;
191             }
192         });
193         
194         // box
195         strats.put(GMLConstants.GML_BOX.toLowerCase(),new ParseStrategy(){
196  
197             public Object parse(Handler arg, GeometryFactory gf) throws SAXException {
198                 // one child, either a coord
199                 // or a coordinate sequence
200                 
201                 if(arg.children.size()<1 || arg.children.size()>2)
202                     throw new SAXException("Cannot create a box without either two coords or one coordinate sequence");
203  
204 //                int srid = getSrid(arg.attrs,gf.getSRID());
205                 
206                 Envelope box = null;
207                 if(arg.children.size() == 1){
208                     CoordinateSequence cs = (CoordinateSequence) arg.children.get(0);
209                     box = cs.expandEnvelope(new Envelope());
210                 }else{
211                     box = new Envelope((Coordinate)arg.children.get(0),(Coordinate)arg.children.get(1));
212                 }
213                 
214                 return box;
215             }
216         });
217         
218         // multi-point
219         strats.put(GMLConstants.GML_MULTI_POINT.toLowerCase(),new ParseStrategy(){
220  
221             public Object parse(Handler arg, GeometryFactory gf) throws SAXException {
222                 // one child, either a coord
223                 // or a coordinate sequence
224                 
225                 if(arg.children.size()<1)
226                     throw new SAXException("Cannot create a multi-point without atleast one point");
227  
228                 int srid = getSrid(arg.attrs,gf.getSRID());
229                 
230                 Point[] pts = (Point[]) arg.children.toArray(new Point[arg.children.size()]);
231                 
232                 MultiPoint mp = gf.createMultiPoint(pts);
233                 
234                 if(mp.getSRID()!=srid)
235                     mp.setSRID(srid);
236                 
237                 return mp;
238             }
239         });
240         
241         // multi-linestring
242         strats.put(GMLConstants.GML_MULTI_LINESTRING.toLowerCase(),new ParseStrategy(){
243  
244             public Object parse(Handler arg, GeometryFactory gf) throws SAXException {
245                 // one child, either a coord
246                 // or a coordinate sequence
247                 
248                 if(arg.children.size()<1)
249                     throw new SAXException("Cannot create a multi-linestring without atleast one linestring");
250  
251                 int srid = getSrid(arg.attrs,gf.getSRID());
252                 
253                 LineString[] lns = (LineString[]) arg.children.toArray(new LineString[arg.children.size()]);
254                 
255                 MultiLineString mp = gf.createMultiLineString(lns);
256                 
257                 if(mp.getSRID()!=srid)
258                     mp.setSRID(srid);
259                 
260                 return mp;
261             }
262         });
263         
264         // multi-poly
265         strats.put(GMLConstants.GML_MULTI_POLYGON.toLowerCase(),new ParseStrategy(){
266  
267             public Object parse(Handler arg, GeometryFactory gf) throws SAXException {
268                 // one child, either a coord
269                 // or a coordinate sequence
270                 
271                 if(arg.children.size()<1)
272                     throw new SAXException("Cannot create a multi-polygon without atleast one polygon");
273  
274                 int srid = getSrid(arg.attrs,gf.getSRID());
275                 
276                 Polygon[] plys = (Polygon[]) arg.children.toArray(new Polygon[arg.children.size()]);
277                 
278                 MultiPolygon mp = gf.createMultiPolygon(plys);
279                 
280                 if(mp.getSRID()!=srid)
281                     mp.setSRID(srid);
282                 
283                 return mp;
284             }
285         });
286         
287         // multi-geom
288         strats.put(GMLConstants.GML_MULTI_GEOMETRY.toLowerCase(),new ParseStrategy(){
289  
290             public Object parse(Handler arg, GeometryFactory gf) throws SAXException {
291                 // one child, either a coord
292                 // or a coordinate sequence
293                 
294                 if(arg.children.size()<1)
295                     throw new SAXException("Cannot create a multi-polygon without atleast one geometry");
296                 
297                 Geometry[] geoms = (Geometry[]) arg.children.toArray(new Geometry[arg.children.size()]);
298                 
299                 GeometryCollection gc = gf.createGeometryCollection(geoms);
300                                 
301                 return gc;
302             }
303         });
304         
305         // coordinates
306         strats.put(GMLConstants.GML_COORDINATES.toLowerCase(),new ParseStrategy(){
307  
308             private WeakHashMap patterns = new WeakHashMap();
309             
310             public Object parse(Handler arg, GeometryFactory gf) throws SAXException {
311                 // one child, either a coord
312                 // or a coordinate sequence
313  
314                 if(arg.text == null || arg.text.length() == 0)
315                     throw new SAXException("Cannot create a coordinate sequence without text to parse");
316                 
317                 String decimal = ".";
318                 String coordSeperator = ",";
319                 String toupleSeperator = " ";
320                 
321                 // get overides from coordinates
322                 if(arg.attrs.getIndex("decimal")>=0)
323                     decimal = arg.attrs.getValue("decimal");
324                 else if(arg.attrs.getIndex(GMLConstants.GML_NAMESPACE,"decimal")>=0)
325                     decimal = arg.attrs.getValue(GMLConstants.GML_NAMESPACE,"decimal");
326  
327                 if(arg.attrs.getIndex("cs")>=0)
328                     coordSeperator = arg.attrs.getValue("cs");
329                 else if(arg.attrs.getIndex(GMLConstants.GML_NAMESPACE,"cs")>=0)
330                     coordSeperator = arg.attrs.getValue(GMLConstants.GML_NAMESPACE,"cs");
331  
332                 if(arg.attrs.getIndex("ts")>=0)
333                     toupleSeperator = arg.attrs.getValue("ts");
334                 else if(arg.attrs.getIndex(GMLConstants.GML_NAMESPACE,"ts")>=0)
335                     toupleSeperator = arg.attrs.getValue(GMLConstants.GML_NAMESPACE,"ts");
336                 
337                 // now to start parse
338                 String t = arg.text.toString();
339                 t = t.replaceAll("\\s"," ");
340                 /**
341                  * Remove spaces after commas, for when they are used as separators (default).
342                  * This prevents coordinates being split by the tuple separator
343                  */
344                 t = t.replaceAll("\\s*,\\s*"",");
345                 
346                 Pattern ptn = (Pattern) patterns.get(toupleSeperator);
347                 if(ptn == null){
348                     String ts = new String(toupleSeperator);
349                     if(ts.indexOf('\\')>-1){
350                         // need to escape it
351                         ts = ts.replaceAll("\\\\","\\\\\\\\");
352                     }
353                     if(ts.indexOf('.')>-1){
354                         // need to escape it
355                         ts = ts.replaceAll("\\.","\\\\.");
356                     }
357                     ptn = Pattern.compile(ts);
358                     patterns.put(toupleSeperator,ptn);
359                 }
360                 String[] touples = ptn.split(t.trim());//  t.trim().split(toupleSeperator);
361                 
362                 if(touples.length == 0)
363                     throw new SAXException("Cannot create a coordinate sequence without a touple to parse");
364                 
365                 // we may have null touples, so calculate the num first
366                 int numNonNullTouples = 0;
367                 for(int i=0;i<touples.length;i++){
368                     if(touples[i] !=null && !"".equals(touples[i].trim())){
369                         if(i!=numNonNullTouples){
370                             touples[numNonNullTouples] = touples[i]; // always shift left
371                         }
372                         numNonNullTouples++;
373                     }
374                 }
375                 for(int i=numNonNullTouples;i<touples.length;i++)
376                     touples[i] = null;
377                 
378                 // null touples now at end of array
379                 if(numNonNullTouples == 0)
380                     throw new SAXException("Cannot create a coordinate sequence without a non-null touple to parse");
381                 
382                 int dim = StringUtil.split(touples[0], coordSeperator).length;
383                 CoordinateSequence cs = gf.getCoordinateSequenceFactory().create(numNonNullTouples,dim);
384                 dim = cs.getDimension(); // max dim
385                 
386                 boolean replaceDec = !".".equals(decimal);
387                 
388                 for(int i=0;i<numNonNullTouples;i++){
389                     // for each touple, split, parse, add
390  
391                     ptn = (Pattern) patterns.get(coordSeperator);
392                     if(ptn == null){
393                         String ts = new String(coordSeperator);
394                         if(ts.indexOf('\\')>-1){
395                             // need to escape it
396                             ts = ts.replaceAll("\\\\","\\\\\\\\");
397                         }
398                         if(ts.indexOf('.')>-1){
399                             // need to escape it
400                             ts = ts.replaceAll("\\.","\\\\.");
401                         }
402                         ptn = Pattern.compile(ts);
403                         patterns.put(coordSeperator,ptn);
404                     }
405                     String[] coords = ptn.split(touples[i]);//  touples[i].split(coordSeperator);
406                     
407                     int dimIndex = 0;
408                     for(int j=0;j<coords.length && j<dim;j++){
409                         if(coords[j] != null && !"".equals(coords[j].trim())){
410                             double ordinate = Double.parseDouble(replaceDec?coords[j].replaceAll(decimal,"."):coords[j]);
411                             cs.setOrdinate(i,dimIndex++,ordinate);
412                         }
413                     }
414                         // fill remaining dim
415                     for(;dimIndex<dim;)cs.setOrdinate(i,dimIndex++,Double.NaN);
416                 }
417                 
418                 return cs;
419             }
420         });
421         
422         // coord
423         strats.put(GMLConstants.GML_COORD.toLowerCase(),new ParseStrategy(){
424  
425             public Object parse(Handler arg, GeometryFactory gf) throws SAXException {
426                 // one child, either a coord
427                 // or a coordinate sequence
428  
429                 if(arg.children.size()<1)
430                     throw new SAXException("Cannot create a coordinate without atleast one axis");
431                 if(arg.children.size()>3)
432                     throw new SAXException("Cannot create a coordinate with more than 3 axis");
433                 
434                 Double[] axis = (Double[]) arg.children.toArray(new Double[arg.children.size()]);
435                 Coordinate c = new Coordinate();
436                 c.x = axis[0].doubleValue();
437                 if(axis.length>1)
438                     c.y = axis[1].doubleValue();
439                 if(axis.length>2)
440                     c.setZ(axis[2].doubleValue());
441                 
442                 return c;
443             }
444         });
445         
446         ParseStrategy coord_child = new ParseStrategy(){
447  
448             public Object parse(Handler arg, GeometryFactory gf) throws SAXException {
449                 if(arg.text == null)
450                     return null;
451                 return Double.valueOf((arg.text.toString()));
452             }
453         };
454         
455         // coord-x
456         strats.put(GMLConstants.GML_COORD_X.toLowerCase(),coord_child);
457         
458         // coord-y
459         strats.put(GMLConstants.GML_COORD_Y.toLowerCase(),coord_child);
460         
461         // coord-z
462         strats.put(GMLConstants.GML_COORD_Z.toLowerCase(),coord_child);
463         
464         ParseStrategy member = new ParseStrategy(){
465  
466             public Object parse(Handler arg, GeometryFactory gf) throws SAXException {
467                 if(arg.children.size()!=1)
468                     throw new SAXException("Geometry Members may only contain one geometry.");
469                 
470                 // type checking will occur in the parent geom collection.
471                 // may wish to add this in the future
472                 
473                 return arg.children.get(0);
474             }
475         };
476         // outerBoundary - linear ring member
477         strats.put(GMLConstants.GML_OUTER_BOUNDARY_IS.toLowerCase(),member);
478         
479         // innerBoundary - linear ring member
480         strats.put(GMLConstants.GML_INNER_BOUNDARY_IS.toLowerCase(),member);
481         
482         // point member
483         strats.put(GMLConstants.GML_POINT_MEMBER.toLowerCase(),member);
484         
485         // line string member
486         strats.put(GMLConstants.GML_LINESTRING_MEMBER.toLowerCase(),member);
487         
488         // polygon member
489         strats.put(GMLConstants.GML_POLYGON_MEMBER.toLowerCase(),member);
490         
491         return strats;
492     }
493     
494     
495     static int getSrid(Attributes attrs, int defaultValue){
496         String srs = null;
497         if(attrs.getIndex(GMLConstants.GML_ATTR_SRSNAME)>=0)
498             srs = attrs.getValue(GMLConstants.GML_ATTR_SRSNAME);
499         else if(attrs.getIndex(GMLConstants.GML_NAMESPACE,GMLConstants.GML_ATTR_SRSNAME)>=0)
500             srs = attrs.getValue(GMLConstants.GML_NAMESPACE,GMLConstants.GML_ATTR_SRSNAME);
501         
502         if(srs != null){
503             srs = srs.trim();
504             if(srs != null && !"".equals(srs)){
505                 try{
506                     return Integer.parseInt(srs);
507                 }catch(NumberFormatException e){
508                   String srsNum = extractIntSuffix(srs);
509                   if (srsNum != null) {
510                       try{
511                           return Integer.parseInt(srsNum);
512                       }catch(NumberFormatException e2){
513                           // ignore
514                       }
515                   }
516                 }
517             }
518         }
519         
520         return defaultValue;
521     }
522     
523     static Pattern PATT_SUFFIX_INT = Pattern.compile("(\\d+)$");
524  
525     static String extractIntSuffix(String s) {
526       Matcher matcher = PATT_SUFFIX_INT.matcher(s);
527       if (matcher.find()) {
528           return matcher.group(1);
529       }
530       return null;
531     }
532     
533     /**
534      * @param uri Not currently used, included for future work
535      * @param localName Used to look up an appropriate parse strategy
536      * @return The ParseStrategy which should be employed
537      * 
538      * @see ParseStrategy
539      */
540     public static ParseStrategy findStrategy(String uri,String localName){
541         return localName == null?null:(ParseStrategy) strategies.get(localName.toLowerCase());
542     }
543 }
544