/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2015, Open Source Geospatial Foundation (OSGeo) * (C) 2014-2015, Boundless * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. */ package org.geotools.data.mongodb; import com.mongodb.BasicDBList; import com.mongodb.BasicDBObject; import com.mongodb.DBObject; import com.vividsolutions.jts.geom.Geometry; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import org.geotools.feature.AttributeTypeBuilder; import org.geotools.feature.simple.SimpleFeatureTypeBuilder; import org.geotools.referencing.CRS; import org.geotools.referencing.crs.DefaultGeographicCRS; import org.opengis.feature.simple.SimpleFeatureType; import org.opengis.feature.type.AttributeDescriptor; import org.opengis.feature.type.GeometryDescriptor; import org.opengis.feature.type.Name; import org.opengis.referencing.FactoryException; import org.opengis.referencing.crs.CoordinateReferenceSystem; /** * * @author tkunicki@boundlessgeo.com */ public class FeatureTypeDBObject { final static String KEY_typeName = "typeName"; final static String KEY_geometryDescriptor = "geometryDescriptor"; final static String KEY_localName = "localName"; final static String KEY_crs = "crs"; final static String KEY_type = "type"; final static String KEY_properties = "properties"; final static String KEY_name = "name"; final static String KEY_defaultValue = "defaultValue"; final static String KEY_minOccurs = "minOccurs"; final static String KEY_maxOccurs = "maxOccurs"; final static String KEY_binding = "binding"; final static String KEY_attributeDescriptors = "attributeDescriptors"; final static String KEY_userData = "userData"; final static String VALUE_name = "name"; final static String PREFIX_URN_OGC = "urn:ogc:def:crs:"; public static DBObject convert(SimpleFeatureType ft) { DBObject ftDBO = new BasicDBObject(KEY_typeName, ft.getTypeName()); Map<String, String> ftUserData = typeCheck(ft.getUserData()); if (!ftUserData.isEmpty()) { ftDBO.put(KEY_userData, new BasicDBObject(ftUserData)); } // for geometry descriptor, just store name to reference against attribute GeometryDescriptor gd = ft.getGeometryDescriptor(); String gdLocalName = gd.getLocalName(); DBObject gdDBO = new BasicDBObject(KEY_localName, gdLocalName); CoordinateReferenceSystem crs = gd.getCoordinateReferenceSystem(); if (crs == null) { crs = DefaultGeographicCRS.WGS84; } DBObject crsDBO = encodeCRSToGeoJSON(crs); if (crsDBO != null) { gdDBO.put(KEY_crs, crsDBO); } ftDBO.put(KEY_geometryDescriptor, gdDBO); BasicDBList adDBL = new BasicDBList(); for (AttributeDescriptor ad : ft.getAttributeDescriptors()) { String adLocalName = ad.getLocalName(); DBObject adDBO = new BasicDBObject(KEY_localName, adLocalName); if (!adLocalName.equals(gdLocalName)) { Object dv = ad.getDefaultValue(); if (dv != null) { adDBO.put(KEY_defaultValue, dv); } adDBO.put(KEY_minOccurs, ad.getMinOccurs()); adDBO.put(KEY_maxOccurs, ad.getMaxOccurs()); } Class<?> binding = ad instanceof GeometryDescriptor ? Geometry.class : ad.getType() .getBinding(); adDBO.put(KEY_type, new BasicDBObject(KEY_binding, binding.getName())); Map<String, String> adUserData = typeCheck(ad.getUserData()); if (!adUserData.isEmpty()) { adDBO.put(KEY_userData, new BasicDBObject(adUserData)); } adDBL.add(adDBO); } ftDBO.put(KEY_attributeDescriptors, adDBL); return ftDBO; } public static SimpleFeatureType convert(DBObject ftDBO) { return convert(ftDBO, null); } public static SimpleFeatureType convert(DBObject ftDBO, Name name) { SimpleFeatureTypeBuilder ftBuilder = new SimpleFeatureTypeBuilder(); if (name == null) { ftBuilder.setName(extractString(ftDBO, KEY_typeName)); } else { ftBuilder.setName(name); } DBObject gdDBO = extractDBObject(ftDBO, KEY_geometryDescriptor); String gdLocalName = extractString(gdDBO, KEY_localName); DBObject crsDBO = extractDBObject(gdDBO, KEY_crs, false); CoordinateReferenceSystem crs = decodeCRSFromGeoJSON(crsDBO); if (crs == null) { crs = DefaultGeographicCRS.WGS84; } AttributeTypeBuilder atBuilder = new AttributeTypeBuilder(); List<?> adDBL = extractDBList(ftDBO, KEY_attributeDescriptors); for (Object adO : adDBL) { if (adO instanceof DBObject) { DBObject adDBO = (DBObject) adO; String adLocalName = extractString(adDBO, KEY_localName); String bindingName = extractString(extractDBObject(adDBO, KEY_type), KEY_binding); try { atBuilder.binding(Class.forName(bindingName)); } catch (ClassNotFoundException ex) { throw new RuntimeException("Unable to generate Class instance for binding " + bindingName); } BasicDBObject adUserDataDBO = extractDBObject(adDBO, KEY_userData, false); if (adUserDataDBO != null) { for (Map.Entry<?,?> entry : ((Map<?, ?>) adUserDataDBO.toMap()).entrySet()) { atBuilder.userData(entry.getKey(), entry.getValue()); } } if (gdLocalName.equals(adLocalName)) { atBuilder.crs(crs); ftBuilder.add(atBuilder.buildDescriptor(adLocalName, atBuilder.buildGeometryType())); } else { Integer min = extractInteger(adDBO, KEY_minOccurs, false); if (min != null) { atBuilder.minOccurs(min); } Integer max = extractInteger(adDBO, KEY_maxOccurs, false); if (max != null) { atBuilder.maxOccurs(max); } Object dv = adDBO.get(KEY_defaultValue); if (dv != null) { atBuilder.defaultValue(dv); } ftBuilder.add(atBuilder.buildDescriptor(adLocalName)); } } } SimpleFeatureType ft = ftBuilder.buildFeatureType(); BasicDBObject ftUserDataDBO = extractDBObject(ftDBO, KEY_userData, false); if (ftUserDataDBO != null) { Map<Object,Object> ftUserData = ft.getUserData(); for (Map.Entry<?,?> entry : ((Map<?, ?>) ftUserDataDBO.toMap()).entrySet()) { ftUserData.put(entry.getKey(), entry.getValue()); } } return ft; } private static Map<String, String> typeCheck(Map<?, ?> map) { Map<String, String> typeChecked = new LinkedHashMap<String, String>(); if (map != null && !map.isEmpty()) { for (Map.Entry<?,?> entry : map.entrySet()) { Object key = entry.getKey(); Object value = entry.getValue(); if (key instanceof String && value instanceof String) { typeChecked.put((String) key, (String) value); } } } return typeChecked; } private static <T> T extractAndVerifyType(Class<T> type, DBObject dbo, String key, boolean required) { Object o = dbo.get(key); if (type.isInstance(o)) { return type.cast(o); } if (required) { throw new RuntimeException("Unable to extract " + key + " with type " + type.getName()); } return null; } static BasicDBObject extractDBObject(DBObject dbo, String key) { return extractDBObject(dbo, key, true); } static BasicDBObject extractDBObject(DBObject dbo, String key, boolean required) { return extractAndVerifyType(BasicDBObject.class, dbo, key, required); } static BasicDBList extractDBList(DBObject dbo, String key) { return extractDBList(dbo, key, true); } static BasicDBList extractDBList(DBObject dbo, String key, boolean required) { return extractAndVerifyType(BasicDBList.class, dbo, key, required); } static String extractString(DBObject dbo, String key) { return extractString(dbo, key, true); } static String extractString(DBObject dbo, String key, boolean required) { return extractAndVerifyType(String.class, dbo, key, required); } static Integer extractInteger(DBObject dbo, String key) { return extractInteger(dbo, key, true); } static Integer extractInteger(DBObject dbo, String key, boolean required) { return extractAndVerifyType(Integer.class, dbo, key, required); } static CoordinateReferenceSystem decodeCRSFromGeoJSON(DBObject crsDBO) { if (crsDBO == null) { return null; } String type = extractString(crsDBO, KEY_type, false); if (type == null || !VALUE_name.equals(type)) { return null; } DBObject pDBO = extractDBObject(crsDBO, KEY_properties, false); if (pDBO == null) { return null; } String name = extractString(pDBO, KEY_name, false); if (name == null) { return null; } CoordinateReferenceSystem crs = null; if (name.startsWith(PREFIX_URN_OGC)) { // GeoJSON 1.0 spec says: // 1) authority must be namespaced with OGC URN. // 2) for geographic CRS axis ordering is (longitude, latitude). // This is a problem as use of the OGC URN will force CRS with *authority* // defined axis ordering. For urn:ogc:def:crs:EPSG:4326 this is // (latitude, longitude). For now just strip off OGC URN as this will // allow geotools to return a CRS with desired axis ordering... name = name.substring(PREFIX_URN_OGC.length()); } try { crs = CRS.decode(name, true); } catch (FactoryException ignore) { } return crs; } static DBObject encodeCRSToGeoJSON(CoordinateReferenceSystem crs) { if (crs == null) { return null; } Integer epsgCode = null; try { epsgCode = CRS.lookupEpsgCode(crs, true); } catch (FactoryException ignore) { } if (epsgCode == null) { return null; } DBObject crsDBO = new BasicDBObject(KEY_type, VALUE_name); crsDBO.put(KEY_properties, new BasicDBObject(KEY_name, PREFIX_URN_OGC + "EPSG:" + epsgCode)); return crsDBO; } }