001: /*
002: * GeoTools - OpenSource mapping toolkit
003: * http://geotools.org
004: * (C) 2005-2006, GeoTools Project Managment Committee (PMC)
005: *
006: * This library is free software; you can redistribute it and/or
007: * modify it under the terms of the GNU Lesser General Public
008: * License as published by the Free Software Foundation;
009: * version 2.1 of the License.
010: *
011: * This library is distributed in the hope that it will be useful,
012: * but WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * Lesser General Public License for more details.
015: */
016: package org.geotools.styling;
017:
018: import java.net.URI;
019: import java.util.ArrayList;
020: import java.util.Hashtable;
021:
022: import org.geotools.data.memory.MemoryDataStore;
023: import org.geotools.feature.AttributeType;
024: import org.geotools.feature.AttributeTypeFactory;
025: import org.geotools.feature.Feature;
026: import org.geotools.feature.FeatureType;
027: import org.geotools.feature.FeatureTypeBuilder;
028: import org.geotools.feature.FeatureTypeFactory;
029: import org.geotools.filter.ExpressionDOMParser;
030: import org.geotools.referencing.CRS;
031: import org.opengis.referencing.crs.CoordinateReferenceSystem;
032: import org.w3c.dom.NamedNodeMap;
033: import org.w3c.dom.Node;
034: import org.w3c.dom.NodeList;
035:
036: import com.vividsolutions.jts.geom.Geometry;
037:
038: public class SLDInlineFeatureParser {
039:
040: /** hash table that takes a epsg# to its definition**/
041: private static Hashtable SRSLookup = new Hashtable();
042:
043: public FeatureType featureType = null;
044: public MemoryDataStore dataStore = null;
045: Node rootNode = null;
046: ArrayList features = new ArrayList();
047: CoordinateReferenceSystem SRS = null; // default EPSG#.
048:
049: private static int uniqueNumber = 0;
050:
051: public SLDInlineFeatureParser(Node root) throws Exception {
052: //handle FeatureCollection or Feature Tag
053:
054: boolean isFeatureCollection = false;
055:
056: if (!(isSimple(root))) //make sure this isnt too complex to parse easily.
057: {
058: throw new Exception(
059: "couldnt parse the SLD Inline features!");//shouldnt get here
060: }
061: Node fc = getNode(root, "FeatureCollection");
062: if (fc != null) {
063: isFeatureCollection = true;
064: root = fc;//decend down one level
065: }
066:
067: featureType = makeFeatureType(root, isFeatureCollection);
068: if (featureType == null)
069: throw new Exception(
070: "SLD InlineFeature Parser - couldnt determine a FeatureType. See help for whats supported.");//shouldnt get here
071:
072: makeFeatures(root, isFeatureCollection);
073: if (features.size() == 0)
074: throw new Exception(
075: "SLD InlineFeature Parser - didnt find any features!");
076:
077: buildStore();
078: }
079:
080: /**
081: * 1. we have a FeatureType (cf. makeFeatureType)
082: * 2. we iterate through either the featureCollection or the _Feature set
083: * 3. we build a Feature for each element of the set
084: * 4. we stick it in the features list
085: *
086: * For example:
087: *
088: * <InlineFeature>
089: * <Dave>
090: * ...
091: * </Dave>
092: * <Dave>
093: * ...
094: * </Dave>
095: * </InlineFeature>
096: *
097: * --- OR ----
098: *
099: * <InlineFeature>
100: * <FeatureCollection>
101: * <featureMember>
102: * <Dave>
103: * ...
104: * </Dave>
105: * </featureMember>
106: * <featureMember>
107: * <Dave>
108: * ...
109: * </Dave>
110: * </featureMember>
111: * </FeatureCollection>
112: * </InlineFeature>
113: *
114: *
115: * @param root will point at either "<InlineFeature>" or "<FeatureCollection>
116: */
117: private void makeFeatures(Node root, boolean isCollection)
118: throws Exception {
119: //iterate through each of the elements inside the root
120:
121: NodeList children = root.getChildNodes();
122:
123: for (int i = 0; i < children.getLength(); i++) {
124: Node child = children.item(i);
125:
126: if ((child == null)
127: || (child.getNodeType() != Node.ELEMENT_NODE)) {
128: continue;
129: }
130: String childName = child.getLocalName();
131: if (childName == null)
132: childName = child.getNodeName();
133: if (childName.equalsIgnoreCase("boundedBy")) //be nice and ignore this
134: continue;
135:
136: if (isCollection) {
137: //decend into the featureMember
138: child = descend(child);
139: }
140: if (child == null)
141: throw new Exception(
142: "SLD inlineFeature Parser - couldnt extract a feature from the dom.");
143:
144: Feature f = parseFeature(child, featureType);
145: features.add(f);
146: }
147: }
148:
149: /**
150: * simple - child points to a <featureMember>, we want it to point to the element inside!
151: *
152: * in general, this will find the 1st element node inside the node.
153: *
154: * @param child
155: */
156: private Node descend(Node root) {
157: NodeList children = root.getChildNodes();
158:
159: for (int i = 0; i < children.getLength(); i++) {
160: Node child = children.item(i);
161:
162: if ((child == null)
163: || (child.getNodeType() != Node.ELEMENT_NODE)) {
164: continue;
165: }
166: return child;
167: }
168: return null; //nothing inside
169: }
170:
171: /**
172: * Parse the feature pointed to by this node.
173: *
174: * See the makeFeatureType() function - this is very similiar except it does a little parsing.
175: *
176: *
177: * @param feature - points to the actual feature ie. "<Person>"
178: * @param featureType
179: */
180: private Feature parseFeature(Node feature, FeatureType featureType)
181: throws Exception {
182: Object[] nullAtts = new Object[featureType.getAttributeCount()]; // initialized to nulls
183: Feature f = featureType.create(nullAtts);
184:
185: NodeList children = feature.getChildNodes();
186: for (int i = 0; i < children.getLength(); i++) {
187: Node child = children.item(i);
188: if ((child == null)
189: || (child.getNodeType() != Node.ELEMENT_NODE)) {
190: continue;
191: }
192: String childName = child.getLocalName();
193: if (childName == null) {
194: childName = child.getNodeName();
195: }
196:
197: Object value = getValue(child);
198: try {
199: f.setAttribute(childName, value);
200: } catch (Exception e) {
201: e.printStackTrace(); // we hid this from the user
202: }
203: }
204: return f;
205: }
206:
207: /**
208: * Given a node, determine if its a geometry or a string attribute
209: * return the corresponding value.
210: * @param child
211: */
212: private Object getValue(Node root) throws Exception {
213: NodeList children = root.getChildNodes();
214: StringBuffer strVal = new StringBuffer();
215:
216: for (int i = 0; i < children.getLength(); i++) {
217: Node child = children.item(i);
218: if ((child == null)) {
219: continue;
220: }
221: if (child.getNodeType() == Node.TEXT_NODE) {
222: strVal.append(child.getNodeValue()); //might get here multiple times -- see sax spec
223: }
224: //we have a nested element! Assume its a geometry
225: if (child.getNodeType() == Node.ELEMENT_NODE) {
226: return parseGeometry(child);
227: }
228: }
229: return strVal;
230: }
231:
232: /**
233: * <Person>
234: * <location>
235: * <gml:Point>...
236: * </gml:Point>
237: * </location>
238: * </Person>
239: *
240: * Decend a level and then pass it off to the geometry parser.
241: *
242: * NOTE: also handles SRS information:
243: *
244: * <gml:Point srsName="http://www.opengis.net/gml/srs/epsg.xml#4326">
245: *
246: * TODO: handle more than just epsg for CRS
247: *
248: * @param root -- points to "<gml:Point>"
249: */
250: private Geometry parseGeometry(Node root) throws Exception {
251: NamedNodeMap atts = root.getAttributes();
252: if (SRS == null) //try to avoid parsing more than once.
253: {
254: Node srsName = atts.getNamedItem("srsName");
255: if (srsName != null) {
256: parseSRS(srsName.getNodeValue());
257: }
258: }
259: return ExpressionDOMParser.parseGML(root);
260: }
261:
262: /**
263: *
264: * Checks to make sure we're not going to shoot ourselves in the foot.
265: * if InlineFeature has a FeatureCollection, then thats the only node
266: * ie. no set of FeatureCollections or FeatureCollection mixed with a set of Feature
267: *
268: * Other stuff as we think of them.
269: *
270: * @param root SLD root node -- "InlineFeature"
271: * @return true if okay, otherwise exception
272: */
273: private boolean isSimple(Node root) throws Exception {
274: // if there's a <FeatureCollection>, thats the only child
275: // if there was a <FeatureCollection>, then descend into it
276: // check to make sure there are only <featureMember> in it
277:
278: int foundFeature = 0;
279: int foundFC = 0;
280:
281: Node fcNode = null;
282: String featureName = null;
283:
284: NodeList children = root.getChildNodes();
285:
286: for (int i = 0; i < children.getLength(); i++) {
287: Node child = children.item(i);
288:
289: if ((child == null)
290: || (child.getNodeType() != Node.ELEMENT_NODE)) {
291: continue;
292: }
293: String childName = child.getLocalName();
294: if (childName == null)
295: childName = child.getNodeName();
296:
297: if (childName.equalsIgnoreCase("FeatureCollection")) {
298: (foundFC)++;
299: fcNode = child;
300: } else {
301: if (featureName == null)
302: featureName = childName;
303: if (!(childName.equalsIgnoreCase(featureName)))
304: throw new Exception(
305: "SLD inline feature parser - it appear that there is >1 feature type present. I got a "
306: + childName
307: + " when I was expecting a "
308: + featureName + " tag");
309: }
310: }
311: if (foundFC > 1)
312: throw new Exception(
313: "SLD - UserLayer, inline feature parser - found >1 FeatureCollection. Not supported");
314: if ((foundFC > 0) && (foundFeature > 0))
315: throw new Exception(
316: "SLD - UserLayer, inline feature parser - found FeatureCollection and featureMembers. Not supported");
317:
318: if (foundFC == 0)
319: return true;
320:
321: featureName = null;
322:
323: //otherwise decend into the featurecollection and check to make sure it only contains features
324: children = fcNode.getChildNodes();
325:
326: for (int i = 0; i < children.getLength(); i++) {
327: Node child = children.item(i);
328:
329: if ((child == null)
330: || (child.getNodeType() != Node.ELEMENT_NODE)) {
331: continue;
332: }
333: String childName = child.getLocalName();
334: if (childName == null)
335: childName = child.getNodeName();
336: if (childName.equalsIgnoreCase("featureMember"))
337: foundFeature++;
338: else if (childName.equalsIgnoreCase("boundedBy")) {
339: //this is okay -- we'll be nice and ignore it.
340: } else if (childName.equalsIgnoreCase("FeatureCollection")) {
341: throw new Exception(
342: "SLD - UserLayer, inline feature parser - found a node of type FeatureCollection. Expected a featureMember - dont support nested collections.");
343:
344: } else
345: throw new Exception(
346: "SLD - UserLayer, inline feature parser - found a node of type '"
347: + child.getLocalName()
348: + "' and dont understand it. Expected a featureMember.");
349: }
350:
351: return true;
352: }
353:
354: /**
355: *
356: */
357: private void buildStore() {
358: dataStore = new MemoryDataStore((Feature[]) features
359: .toArray(new Feature[features.size()]));
360: }
361:
362: /**
363: * 1. get an actual Feature Node
364: * 2. go through each of its subtags
365: * 3. if that subtag contains a geometry, then it an attribute of type geometry, otherwise string
366: *
367: * NOTE: we set the namespace to be "http://temp.inline.feature.sld.com"
368: * NOTE: we set the featuretype name to be whatever the enclosing tag is. For example:
369: * <FeatureCollection>
370: * <gml:featureMember>
371: * <tiger:point_landmark fid="point_landmark.490053">
372: * <tiger:wkb_geometry>
373: * <gml:Point srsName="http://www.opengis.net/gml/srs/epsg.xml#4326">
374: * <gml:coordinates decimal="." cs="," ts=" ">-73.983597,40.736308</gml:coordinates>
375: * </gml:Point>
376: * </tiger:wkb_geometry>
377: * <tiger:laname>Cabrini Medical Center</tiger:laname>
378: * </tiger:point_landmark>
379: * <gml:featureMember>
380: * </FeatureCollection>
381: *
382: * Would have a Featuretype name of "tiger:wkb_geometry" with 2 attributes:
383: * wkb_geometry -- geometry
384: * laname -- string
385: *
386: * @param root
387: */
388: private FeatureType makeFeatureType(Node root, boolean isCollection)
389: throws Exception {
390: Node feature = null;
391: //get a Feature node
392: Node featureMember = root;
393: if (isCollection)
394: featureMember = getNode(root, "featureMember");
395:
396: //next node under featureMember what we want. Unless its a boundedBy, in which case we dont want it.
397: NodeList children = featureMember.getChildNodes();
398:
399: // look for next element that's not "boundedBy"
400: for (int i = 0; i < children.getLength(); i++) {
401: Node child = children.item(i);
402: if ((child == null)
403: || (child.getNodeType() != Node.ELEMENT_NODE)) {
404: continue;
405: }
406: String childName = child.getLocalName();
407: if (childName == null) {
408: childName = child.getNodeName();
409: }
410: if (!(childName.equalsIgnoreCase("boundedBy"))) {
411: feature = child;
412: break;
413: }
414: }
415: if (feature == null)
416: throw new Exception(
417: "couldnt find a Feature in the Inline Features!");
418:
419: //okay, we have a feature, we now need to figure out its feature type.
420: // method:
421: // step through each node, its name is a new element in the Feature
422: // we look for any internal tags (nesting). There there are, we check to see if its a geometry
423: // otherwise the type is string.
424: // simple!
425:
426: String featureName = feature.getLocalName();
427: if (featureName == null) {
428: featureName = feature.getNodeName();
429: }
430:
431: //DJB:I considered making each featuretype unique (thats the uniquenumber), but decided against
432: // it so that the standard feature type filtering stuff would work ("<FeatureTypeStyle>"
433: // and <FeatureTypeConsraint>
434: FeatureTypeBuilder build = FeatureTypeFactory
435: .newInstance(featureName);
436: build.setName(featureName);
437: build
438: .setNamespace(new URI(
439: "http://temp.inline.feature.sld.com"));
440:
441: children = feature.getChildNodes();
442: for (int i = 0; i < children.getLength(); i++) {
443: Node child = children.item(i);
444: if ((child == null)
445: || (child.getNodeType() != Node.ELEMENT_NODE)) {
446: continue;
447: }
448: String childName = child.getLocalName();
449: if (childName == null) {
450: childName = child.getNodeName();
451: }
452: AttributeType attType = null;
453: //okay, have a tag, check to see if its a geometry
454: if (isGeometry(child)) {
455: // force full geometry parsing so that we get to know the declared SRS
456: getValue(child);
457: attType = AttributeTypeFactory.newAttributeType(
458: childName, Geometry.class, true, 0, null, SRS);
459: } else {
460: attType = AttributeTypeFactory.newAttributeType(
461: childName, String.class);
462: }
463: build.addType(attType);
464: }
465: return build.getFeatureType();
466: }
467:
468: /**
469: * looks for a nested attribute - assumes that this is a geometry.
470: * TODO: be much smarter
471: * @param child
472: */
473: private boolean isGeometry(Node root) {
474: NodeList children = root.getChildNodes();
475: for (int i = 0; i < children.getLength(); i++) {
476: Node child = children.item(i);
477: if ((child == null)
478: || (child.getNodeType() != Node.ELEMENT_NODE)) {
479: continue;
480: }
481: //we have a nested element!
482: return true;
483: }
484: return false;
485: }
486:
487: /**
488: * Give a node and the name of a child of that node, find its (string) value.
489: * This doesnt do anything complex.
490: *
491: * @param parentNode
492: * @param wantedChildName
493: */
494: public Node getNode(Node parentNode, String wantedChildName) {
495: NodeList children = parentNode.getChildNodes();
496:
497: for (int i = 0; i < children.getLength(); i++) {
498: Node child = children.item(i);
499:
500: if ((child == null)
501: || (child.getNodeType() != Node.ELEMENT_NODE)) {
502: continue;
503: }
504: String childName = child.getLocalName();
505: if (childName == null) {
506: childName = child.getNodeName();
507: }
508: if (childName.equalsIgnoreCase(wantedChildName)) {
509: return child;
510: }
511: }
512: return null;
513: }
514:
515: public synchronized int getUID() {
516: return uniqueNumber++;
517: }
518:
519: /**
520: * expected input:
521: * "http://www.opengis.net/gml/srs/epsg.xml#4326"
522: * NOTE: only supports epsg#s.
523: * @param srs
524: */
525: private void parseSRS(String srs) throws Exception {
526: if (srs == null)
527: return;
528: String epsgCode = srs.substring(srs.indexOf('#') + 1);
529: int srsnum = Integer.parseInt(epsgCode);
530: SRS = getSRS(srsnum);
531: }
532:
533: /**
534: * simple way of getting epsg #.
535: * We cache them so that we dont have to keep reading the DB or the epsg.properties file.
536: * I cannot image a system with more than a dozen CRSs in it...
537: *
538: * @param epsg
539: */
540: private CoordinateReferenceSystem getSRS(int epsg) throws Exception {
541: CoordinateReferenceSystem result = (CoordinateReferenceSystem) SRSLookup
542: .get(new Integer(epsg));
543: if (result == null) {
544: //make and add to hash
545: result = CRS.decode("EPSG:" + epsg);
546: SRSLookup.put(new Integer(epsg), result);
547: }
548: return result;
549: }
550:
551: }
|