You can subscribe to this list here.
2001 |
Jan
|
Feb
|
Mar
|
Apr
|
May
|
Jun
|
Jul
|
Aug
|
Sep
|
Oct
|
Nov
(39) |
Dec
(70) |
---|---|---|---|---|---|---|---|---|---|---|---|---|
2002 |
Jan
(52) |
Feb
(168) |
Mar
(248) |
Apr
(143) |
May
(418) |
Jun
(558) |
Jul
(702) |
Aug
(311) |
Sep
(141) |
Oct
(350) |
Nov
(172) |
Dec
(182) |
2003 |
Jan
(320) |
Feb
(362) |
Mar
(356) |
Apr
(218) |
May
(447) |
Jun
(203) |
Jul
(745) |
Aug
(494) |
Sep
(175) |
Oct
(422) |
Nov
(554) |
Dec
(162) |
2004 |
Jan
(217) |
Feb
(353) |
Mar
(228) |
Apr
(407) |
May
(211) |
Jun
(270) |
Jul
(264) |
Aug
(198) |
Sep
(268) |
Oct
(227) |
Nov
(118) |
Dec
(47) |
2005 |
Jan
(207) |
Feb
(243) |
Mar
(297) |
Apr
(197) |
May
(281) |
Jun
(166) |
Jul
(164) |
Aug
(92) |
Sep
(155) |
Oct
(196) |
Nov
(189) |
Dec
(114) |
2006 |
Jan
(129) |
Feb
(219) |
Mar
(274) |
Apr
(213) |
May
(245) |
Jun
(220) |
Jul
(376) |
Aug
(347) |
Sep
(179) |
Oct
(493) |
Nov
(448) |
Dec
(339) |
2007 |
Jan
(304) |
Feb
(273) |
Mar
(237) |
Apr
(186) |
May
(215) |
Jun
(320) |
Jul
(229) |
Aug
(313) |
Sep
(331) |
Oct
(279) |
Nov
(347) |
Dec
(266) |
2008 |
Jan
(332) |
Feb
(280) |
Mar
(203) |
Apr
(277) |
May
(301) |
Jun
(356) |
Jul
(292) |
Aug
(203) |
Sep
(277) |
Oct
(142) |
Nov
(210) |
Dec
(239) |
2009 |
Jan
(250) |
Feb
(193) |
Mar
(174) |
Apr
(183) |
May
(342) |
Jun
(230) |
Jul
(292) |
Aug
(161) |
Sep
(204) |
Oct
(280) |
Nov
(281) |
Dec
(175) |
2010 |
Jan
(113) |
Feb
(106) |
Mar
(199) |
Apr
(166) |
May
(298) |
Jun
(147) |
Jul
(175) |
Aug
(192) |
Sep
(71) |
Oct
(79) |
Nov
(58) |
Dec
(55) |
2011 |
Jan
(83) |
Feb
(169) |
Mar
(142) |
Apr
(207) |
May
(311) |
Jun
(183) |
Jul
(218) |
Aug
(190) |
Sep
(158) |
Oct
(197) |
Nov
(93) |
Dec
(74) |
2012 |
Jan
(92) |
Feb
(50) |
Mar
(64) |
Apr
(45) |
May
(100) |
Jun
(70) |
Jul
(3) |
Aug
(1) |
Sep
(2) |
Oct
(5) |
Nov
(7) |
Dec
(4) |
2013 |
Jan
(6) |
Feb
(2) |
Mar
(2) |
Apr
(4) |
May
(3) |
Jun
|
Jul
(2) |
Aug
|
Sep
|
Oct
(1) |
Nov
(1) |
Dec
|
2014 |
Jan
(2) |
Feb
(2) |
Mar
(2) |
Apr
(3) |
May
(3) |
Jun
(1) |
Jul
|
Aug
(4) |
Sep
|
Oct
(1) |
Nov
(1) |
Dec
|
2015 |
Jan
|
Feb
|
Mar
|
Apr
|
May
|
Jun
(1) |
Jul
|
Aug
(1) |
Sep
|
Oct
|
Nov
(1) |
Dec
(1) |
2016 |
Jan
|
Feb
|
Mar
|
Apr
|
May
(1) |
Jun
(1) |
Jul
|
Aug
(3) |
Sep
|
Oct
|
Nov
(1) |
Dec
|
2017 |
Jan
(1) |
Feb
|
Mar
|
Apr
|
May
|
Jun
|
Jul
|
Aug
|
Sep
|
Oct
|
Nov
|
Dec
|
Author: ang05a Date: 2012-06-19 19:50:19 -0700 (Tue, 19 Jun 2012) New Revision: 38818 Modified: trunk/modules/extension/app-schema/app-schema/src/main/java/org/geotools/data/complex/config/AppSchemaDataAccessConfigurator.java trunk/modules/extension/app-schema/app-schema/src/main/java/org/geotools/data/complex/config/FeatureTypeRegistry.java trunk/modules/extension/app-schema/app-schema/src/test/java/org/geotools/data/complex/config/EmfAppSchemaReaderTest.java Log: GEOT-4114: Substitution group errors in FeatureTypeRegistry Modified: trunk/modules/extension/app-schema/app-schema/src/main/java/org/geotools/data/complex/config/AppSchemaDataAccessConfigurator.java =================================================================== --- trunk/modules/extension/app-schema/app-schema/src/main/java/org/geotools/data/complex/config/AppSchemaDataAccessConfigurator.java 2012-06-19 10:16:38 UTC (rev 38817) +++ trunk/modules/extension/app-schema/app-schema/src/main/java/org/geotools/data/complex/config/AppSchemaDataAccessConfigurator.java 2012-06-20 02:50:19 UTC (rev 38818) @@ -201,6 +201,7 @@ } finally { if (typeRegistry != null) { typeRegistry.disposeSchemaIndexes(); + typeRegistry = null; } } } @@ -260,7 +261,7 @@ target.getType().getUserData().put("schemaURI", schemaURIs); List attMappings = getAttributeMappings(target, dto.getAttributeMappings(), dto - .getItemXpath()); + .getItemXpath(), crs); FeatureTypeMapping mapping; @@ -273,12 +274,10 @@ } featureTypeMappings.add(mapping); } catch (Exception e) { - LOGGER - .warning("Error creating app-schema data store for '" - + dto.getMappingName() != null ? dto.getMappingName() : dto - .getTargetElementName() - + "', caused by: " + e.getMessage()); - throw new IOException(e.getMessage()); + LOGGER.warning("Error creating app-schema data store for '" + + (dto.getMappingName() != null ? dto.getMappingName() : dto + .getTargetElementName()) + "', caused by: " + e.getMessage()); + throw new IOException(e); } } return featureTypeMappings; @@ -289,8 +288,7 @@ String prefixedTargetName = dto.getTargetElementName(); Name targetNodeName = Types.degloseName(prefixedTargetName, namespaces); - AttributeDescriptor targetDescriptor = typeRegistry.getDescriptor(targetNodeName, crs, dto - .getAttributeMappings()); + AttributeDescriptor targetDescriptor = typeRegistry.getDescriptor(targetNodeName, null, null, crs); if (targetDescriptor == null) { throw new NoSuchElementException("descriptor " + targetNodeName + " not found in parsed schema"); @@ -308,7 +306,7 @@ * @return */ private List getAttributeMappings(final AttributeDescriptor root, final List attDtos, - String itemXpath) throws IOException { + String itemXpath, CoordinateReferenceSystem crs) throws IOException { List attMappings = new LinkedList(); for (Iterator it = attDtos.iterator(); it.hasNext();) { @@ -363,8 +361,7 @@ if (expectedInstanceTypeName != null) { Name expectedNodeTypeName = Types.degloseName(expectedInstanceTypeName, namespaces); - expectedInstanceOf = typeRegistry - .getAttributeType(expectedNodeTypeName, null, null); + expectedInstanceOf = typeRegistry.getAttributeType(expectedNodeTypeName, null, crs); if (expectedInstanceOf == null) { String msg = "mapping expects and instance of " + expectedNodeTypeName + " for attribute " + targetXPath Modified: trunk/modules/extension/app-schema/app-schema/src/main/java/org/geotools/data/complex/config/FeatureTypeRegistry.java =================================================================== --- trunk/modules/extension/app-schema/app-schema/src/main/java/org/geotools/data/complex/config/FeatureTypeRegistry.java 2012-06-19 10:16:38 UTC (rev 38817) +++ trunk/modules/extension/app-schema/app-schema/src/main/java/org/geotools/data/complex/config/FeatureTypeRegistry.java 2012-06-20 02:50:19 UTC (rev 38818) @@ -38,7 +38,9 @@ import org.eclipse.xsd.XSDElementDeclaration; import org.eclipse.xsd.XSDParticle; import org.eclipse.xsd.XSDTypeDefinition; +import org.geotools.feature.NameImpl; import org.geotools.feature.Types; +import org.geotools.feature.type.AbstractLazyComplexTypeImpl; import org.geotools.feature.type.ComplexFeatureTypeFactoryImpl; import org.geotools.feature.type.GeometryTypeImpl; import org.geotools.gml3.GMLConfiguration; @@ -158,24 +160,7 @@ } typeRegistry.putAll(FOUNDATION_TYPES); - - // set substitution group for basic types - // since the current solution only works for created types (for non basic types) - for (AttributeType type : typeRegistry.values()) { - if (type instanceof ComplexType) { - XSDTypeDefinition typeDef = null; - try { - typeDef = getTypeDefinition(type.getName()); - if (typeDef instanceof XSDComplexTypeDefinition) { - setSubstitutionGroup((ComplexType) type, - (XSDComplexTypeDefinition) typeDef, null, null); - } - } catch (IllegalArgumentException e) { - LOGGER.log(Level.WARNING, - "Unable to set substitution group, caused by: " + e.getMessage()); - } - } - } + descriptorRegistry.putAll(FOUNDATION_DESCRIPTORS); } @@ -196,25 +181,32 @@ } public AttributeDescriptor getDescriptor(final Name descriptorName, - CoordinateReferenceSystem crs, List<AttributeMapping> attMappings) { + XSDComplexTypeDefinition typeDef, XSDElementDeclaration elemDecl, + CoordinateReferenceSystem crs) { AttributeDescriptor descriptor = descriptorRegistry.get(descriptorName); if (descriptor == null) { try { - XSDElementDeclaration elemDecl = getElementDeclaration(descriptorName); - descriptor = createAttributeDescriptor(null, elemDecl, crs, attMappings); - LOGGER.finest("Registering attribute descriptor " + descriptor.getName()); - register(descriptor); + if (elemDecl == null) { + elemDecl = getElementDeclaration(descriptorName); + } } catch (NoSuchElementException e) { - LOGGER.log(Level.WARNING, e.getMessage()); + String msg = "Type not found for " + + descriptorName + + " at type container " + + (typeDef == null ? null : typeDef.getTargetNamespace() + "#" + + typeDef.getName() + " at " + + typeDef.getSchema().getSchemaLocation()); + NoSuchElementException nse = new NoSuchElementException(msg); + nse.initCause(e); + throw nse; } + descriptor = createAttributeDescriptor(typeDef, elemDecl, crs); + LOGGER.finest("Registering attribute descriptor " + descriptor.getName()); + register(descriptor); } return descriptor; } - public AttributeDescriptor getDescriptor(final Name descriptorName) { - return getDescriptor(descriptorName, null, null); - } - private XSDElementDeclaration getElementDeclaration(final Name descriptorName) { QName qname = Types.toQName(descriptorName); @@ -226,79 +218,114 @@ } } if (elemDecl == null) { - throw new NoSuchElementException("No top level element found in schemas: " + qname); + String msg = "No top level element found in schemas: " + qname; + LOGGER.log(Level.WARNING, msg); + throw new NoSuchElementException(msg); } return elemDecl; } - + public AttributeType getAttributeType(final Name typeName) { return getAttributeType(typeName, null, null); } - public AttributeType getAttributeType(final Name typeName, CoordinateReferenceSystem crs, - List<AttributeMapping> attMappings) { + public AttributeType getAttributeType(final Name typeName, XSDTypeDefinition xsdType, + CoordinateReferenceSystem crs) { AttributeType type = (AttributeType) typeRegistry.get(typeName); - if (type == null) { - XSDTypeDefinition typeDef = getTypeDefinition(typeName); - LOGGER.finest("Creating attribute type " + typeDef.getQName()); - type = createType(typeDef, crs, attMappings); - LOGGER.finest("Registering attribute type " + type.getName()); - } + if (type == null || type instanceof AbstractLazyComplexTypeImpl) { + // recreate lazy types too + if (xsdType == null) { + xsdType = getTypeDefinition(typeName); + } + LOGGER.finest("Creating attribute type " + typeName); + type = createType(typeName, xsdType, crs, false); + LOGGER.finest("Registering attribute type " + typeName); + } return type; } - private void setSubstitutionGroup(ComplexType complexType, XSDComplexTypeDefinition typeDef, - CoordinateReferenceSystem crs, List attMappings) { - List children = Schemas.getChildElementParticles(typeDef, true); + private void setSubstitutionGroup(XSDComplexTypeDefinition container, + XSDElementDeclaration elemDecl, PropertyDescriptor descriptor, + CoordinateReferenceSystem crs) { - final Collection<PropertyDescriptor> schema = new ArrayList<PropertyDescriptor>( - children.size()); + if (descriptor.getUserData().get("substitutionGroup") != null) { + // this has been done before + return; + } - XSDElementDeclaration childDecl; - Name typeName; - for (Iterator it = children.iterator(); it.hasNext();) { - childDecl = (XSDElementDeclaration) ((XSDParticle) it.next()).getContent(); - if (childDecl.isElementDeclarationReference()) { - childDecl = childDecl.getResolvedElementDeclaration(); + List<AttributeDescriptor> substitutionGroup = new ArrayList<AttributeDescriptor>(); + descriptor.getUserData().put("substitutionGroup", substitutionGroup); + + int minOccurs = Schemas.getMinOccurs(container, elemDecl); + int maxOccurs = Schemas.getMaxOccurs(container, elemDecl); + boolean nillable = elemDecl.isNillable(); + + Iterator substitutions = elemDecl.getSubstitutionGroup().iterator(); + XSDElementDeclaration sub; + while (substitutions.hasNext()) { + sub = (XSDElementDeclaration) substitutions.next(); + if (!(sub.getName().equals(elemDecl.getName())) + || !(sub.getTargetNamespace().equals(elemDecl.getTargetNamespace()))) { + Name elemName = Types.typeName(sub.getTargetNamespace(), sub.getName()); + AttributeType type = getTypeOf(sub, crs); + if (type != null) { + substitutionGroup.add(createAttributeDescriptor(type, crs, elemName, minOccurs, + maxOccurs, nillable, null)); + } } - typeName = Types.typeName(childDecl.getTargetNamespace(), childDecl.getName()); - try { - PropertyDescriptor descriptor = complexType.getDescriptor(typeName); - if (descriptor != null) { - if (!descriptor.getUserData().containsValue("substitutionGroup")) { - List<AttributeDescriptor> substitutionGroup = new ArrayList<AttributeDescriptor>(); - descriptor.getUserData().put("substitutionGroup", substitutionGroup); - Iterator subIt = childDecl.getSubstitutionGroup().iterator(); - while (subIt.hasNext()) { - XSDElementDeclaration sub = (XSDElementDeclaration) subIt.next(); - if (!sub.getName().equals(childDecl.getName())) { - Name elemName = Types.typeName(sub.getTargetNamespace(), - sub.getName()); - XSDTypeDefinition subType = sub.getTypeDefinition(); - Name name = subType.getName() == null ? elemName : Types.typeName( - subType.getTargetNamespace(), subType.getName()); - AttributeType type = typeRegistry.get(name); - if (type != null) { - int minOccurs = Schemas.getMinOccurs(typeDef, childDecl); - int maxOccurs = Schemas.getMaxOccurs(typeDef, childDecl); - boolean nillable = childDecl.isNillable(); - AttributeDescriptor subDescriptor = createAttributeDescriptor( - type, crs, elemName, minOccurs, maxOccurs, nillable, - null); - substitutionGroup.add(subDescriptor); - } + } + + XSDTypeDefinition typeDef = elemDecl.getType(); + + if (typeDef instanceof XSDComplexTypeDefinition) { + Name typeName = Types.typeName(typeDef.getTargetNamespace(), typeDef.getName()); + AttributeType attType = typeRegistry.get(typeName); + + if (!processingTypes.contains(typeName)) { + // ignore processingTypes to avoid endless recursion + if (attType == null || attType instanceof AbstractLazyComplexTypeImpl) { + // type is not yet registered or it's a lazy type from foundation types + // recreate lazy type to ensure everything is loaded + // it will eventually call this method so substitution groups will be set then + LOGGER.finest("Creating attribute type " + typeName); + createType(typeName, typeDef, crs, false); + LOGGER.finest("Registering attribute type " + typeName); + } else if (attType instanceof ComplexType) { + // ensure substitution groups are set for children including non lazy foundation + // types + ComplexType complexType = (ComplexType) attType; + Collection<PropertyDescriptor> children = complexType.getDescriptors(); + + List<XSDParticle> childParticles = Schemas.getChildElementParticles(typeDef, + true); + + for (XSDParticle particle : childParticles) { + XSDElementDeclaration element = (XSDElementDeclaration) particle + .getContent(); + + if (element.isElementDeclarationReference()) { + element = element.getResolvedElementDeclaration(); + } + PropertyDescriptor childDesc = null; + for (PropertyDescriptor desc : children) { + if (desc.getName().getLocalPart().equals(element.getName()) + && desc.getName().getNamespaceURI() + .equals(element.getTargetNamespace())) { + childDesc = desc; + break; } } + if (childDesc != null) { + setSubstitutionGroup((XSDComplexTypeDefinition) typeDef, element, + childDesc, crs); + } } } - } catch (Exception e) { - LOGGER.log(Level.WARNING, - "Could not create substitution descriptor: " + e.getMessage()); } } } - + private XSDTypeDefinition getTypeDefinition(Name typeName) { QName qName = Types.toQName(typeName); XSDTypeDefinition typeDefinition = null; @@ -333,12 +360,11 @@ } private AttributeDescriptor createAttributeDescriptor(final XSDComplexTypeDefinition container, - final XSDElementDeclaration elemDecl, CoordinateReferenceSystem crs, - List<AttributeMapping> attMappings) { + final XSDElementDeclaration elemDecl, CoordinateReferenceSystem crs) { int minOccurs = container == null ? 0 : Schemas.getMinOccurs(container, elemDecl); int maxOccurs = container == null ? Integer.MAX_VALUE : Schemas.getMaxOccurs(container, elemDecl); - return createAttributeDescriptor( container, elemDecl, minOccurs, maxOccurs, crs, attMappings, true); + return createAttributeDescriptor(elemDecl, minOccurs, maxOccurs, crs); } @@ -346,73 +372,38 @@ CoordinateReferenceSystem crs, Name elemName, int minOccurs, int maxOccurs, boolean nillable, Object defaultValue) { AttributeDescriptor descriptor = null; - try { - if (!(type instanceof AttributeTypeProxy) - && Geometry.class.isAssignableFrom(type.getBinding())) { - // create geometry descriptor with the simple feature type CRS - GeometryType geomType = new GeometryTypeImpl(type.getName(), type.getBinding(), - crs, type.isIdentified(), type.isAbstract(), type.getRestrictions(), - type.getSuper(), type.getDescription()); - descriptor = typeFactory.createGeometryDescriptor(geomType, elemName, minOccurs, - maxOccurs, nillable, defaultValue); - } else { - descriptor = typeFactory.createAttributeDescriptor(type, elemName, minOccurs, - maxOccurs, nillable, defaultValue); - } - } catch (Exception e) { - LOGGER.log(Level.WARNING, "Could not create substitution descriptor: " + e.getMessage()); + if (maxOccurs == -1) { + // this happens when maxOccurs is set to "unbounded" + maxOccurs = Integer.MAX_VALUE; } + + if (!(type instanceof AttributeTypeProxy) + && Geometry.class.isAssignableFrom(type.getBinding())) { + // create geometry descriptor with the simple feature type CRS + GeometryType geomType = new GeometryTypeImpl(type.getName(), type.getBinding(), crs, + type.isIdentified(), type.isAbstract(), type.getRestrictions(), + type.getSuper(), type.getDescription()); + descriptor = typeFactory.createGeometryDescriptor(geomType, elemName, minOccurs, + maxOccurs, nillable, defaultValue); + } else { + descriptor = typeFactory.createAttributeDescriptor(type, elemName, minOccurs, + maxOccurs, nillable, defaultValue); + } return descriptor; } - private AttributeDescriptor createAttributeDescriptor(final XSDComplexTypeDefinition container, - final XSDElementDeclaration elemDecl, int minOccurs, int maxOccurs, CoordinateReferenceSystem crs, - List<AttributeMapping> attMappings, boolean useSubstitutionGroups) { + private AttributeDescriptor createAttributeDescriptor( + final XSDElementDeclaration elemDecl, int minOccurs, int maxOccurs, CoordinateReferenceSystem crs) { String targetNamespace = elemDecl.getTargetNamespace(); String name = elemDecl.getName(); Name elemName = Types.typeName(targetNamespace, name); - - AttributeType type; - try { - type = getTypeOf(elemDecl, crs, attMappings); - } catch (NoSuchElementException e) { - String msg = "Type not found for " + elemName + " at type container " - + (container == null ? null : container.getTargetNamespace() + "#" + container.getName() + " at " - + container.getSchema().getSchemaLocation()); - NoSuchElementException nse = new NoSuchElementException(msg); - nse.initCause(e); - throw nse; - } + AttributeType type = getTypeOf(elemDecl, crs); boolean nillable = elemDecl.isNillable(); - - if (maxOccurs == -1) { - // this happens when maxOccurs is set to "unbounded" - maxOccurs = Integer.MAX_VALUE; - } Object defaultValue = null; AttributeDescriptor descriptor = createAttributeDescriptor(type, crs, elemName, minOccurs, maxOccurs, nillable, defaultValue); descriptor.getUserData().put(XSDElementDeclaration.class, elemDecl); - //NC - substitution groups - if (useSubstitutionGroups) { - List<AttributeDescriptor> substitutionGroup = new ArrayList<AttributeDescriptor>(); - Iterator it = elemDecl.getSubstitutionGroup().iterator(); - while ( it.hasNext()){ - XSDElementDeclaration sub = (XSDElementDeclaration)it.next(); - if (!sub.getName().equals (elemDecl.getName())) { - try { - substitutionGroup.add( createAttributeDescriptor ( container , sub, minOccurs, maxOccurs, crs, attMappings, false)) ; - } catch (Exception e) { - LOGGER.log(Level.WARNING, "Could not create substitution descriptor: " + e.getMessage()); - } - } - } - - descriptor.getUserData().put("substitutionGroup", substitutionGroup); - } - //NC - end. - return descriptor; } @@ -424,9 +415,7 @@ * @param elemDecl * @return */ - private AttributeType getTypeOf(XSDElementDeclaration elemDecl, CoordinateReferenceSystem crs, - List<AttributeMapping> attMappings) { - boolean hasToBeRegistered = false; + private AttributeType getTypeOf(XSDElementDeclaration elemDecl, CoordinateReferenceSystem crs) { XSDTypeDefinition typeDefinition; // TODO REVISIT, I'm not sure this is the way to find out if the @@ -437,31 +426,44 @@ } typeDefinition = elemDecl.getAnonymousTypeDefinition(); if (typeDefinition == null) { - hasToBeRegistered = true; typeDefinition = elemDecl.getTypeDefinition(); } + + if (typeDefinition == null) { + // last resort.. look in the lazy schemas + QName qname = Types.toQName(Types.typeName(elemDecl.getTargetNamespace(), + elemDecl.getName())); + for (SchemaIndex schemaIndex : schemas) { + elemDecl = schemaIndex.getElementDeclaration(qname); + if (elemDecl != null) { + break; + } + } + if (elemDecl != null) { + if (elemDecl.isElementDeclarationReference()) { + elemDecl = elemDecl.getResolvedElementDeclaration(); + } + typeDefinition = elemDecl.getAnonymousTypeDefinition(); + if (typeDefinition == null) { + typeDefinition = elemDecl.getTypeDefinition(); + } + } + } if (typeDefinition == null) { - throw new NoSuchElementException("The element declaration " + String msg = "The element declaration " + elemDecl.getTargetNamespace() + "#" + elemDecl.getName() - + " has a null type definition, can't continue, fix it on the schema"); + + " has a null type definition, can't continue, fix it on the schema"; + LOGGER.warning(msg); + throw new NoSuchElementException(msg); } - AttributeType type; - if (hasToBeRegistered) { - String targetNamespace = typeDefinition.getTargetNamespace(); - String name = typeDefinition.getName(); - Name typeName = Types.typeName(targetNamespace, name); - type = getAttributeType(typeName, crs, attMappings); - if (type == null) { - type = createType(typeName, typeDefinition, crs, attMappings, false); - } - } else { - String name = elemDecl.getName(); - String targetNamespace = elemDecl.getTargetNamespace(); - Name overrideName = Types.typeName(targetNamespace, name); - type = createType(overrideName, typeDefinition, crs, attMappings, true); - } - return type; + + String targetNamespace = typeDefinition.getTargetNamespace(); + String name = typeDefinition.getName(); + Name typeName = Types.typeName(targetNamespace, name); + + return getAttributeType(typeName, typeDefinition, crs); + } private AttributeType createProxiedType(final Name assignedName, @@ -502,14 +504,6 @@ return isDerivedFrom(typeDefinition, typeName); } - private AttributeType createType(XSDTypeDefinition typeDefinition, - CoordinateReferenceSystem crs, List<AttributeMapping> attMappings) { - String targetNamespace = typeDefinition.getTargetNamespace(); - String name = typeDefinition.getName(); - Name typeName = Types.typeName(targetNamespace, name); - return createType(typeName, typeDefinition, crs, attMappings, false); - } - /** * Creates an {@link AttributeType} that matches the xsd type definition as much as possible. * <p> @@ -529,7 +523,7 @@ */ private AttributeType createType(final Name assignedName, final XSDTypeDefinition typeDefinition, CoordinateReferenceSystem crs, - List<AttributeMapping> attMappings, boolean anonymous) { + boolean anonymous) { AttributeType attType; // ///////// @@ -551,11 +545,9 @@ String targetNamespace = baseType.getTargetNamespace(); String name = baseType.getName(); if (name != null) { - superType = getType(targetNamespace, name); + Name baseTypeName = new NameImpl(targetNamespace, name); + superType = getAttributeType(baseTypeName, baseType, crs); } - if (superType == null) { - superType = createType(baseType, crs, attMappings); - } } else { LOGGER.warning(assignedName + " has no super type"); } @@ -564,7 +556,7 @@ XSDComplexTypeDefinition complexTypeDef; complexTypeDef = (XSDComplexTypeDefinition) typeDefinition; boolean includeParents = true; - List children = Schemas.getChildElementDeclarations(typeDefinition, includeParents); + List<XSDElementDeclaration> children = Schemas.getChildElementDeclarations(typeDefinition, includeParents); final Collection<PropertyDescriptor> schema = new ArrayList<PropertyDescriptor>( children.size()); @@ -574,15 +566,35 @@ for (Iterator it = children.iterator(); it.hasNext();) { childDecl = (XSDElementDeclaration) it.next(); try { - descriptor = createAttributeDescriptor(complexTypeDef, childDecl, crs, - attMappings); - schema.add(descriptor); + descriptor = createAttributeDescriptor(complexTypeDef, childDecl, crs); } catch (NoSuchElementException e) { - LOGGER.log(Level.WARNING, e.getMessage()); - throw e; + String msg = "Failed to create descriptor for '" + + childDecl.getTargetNamespace() + "#" + childDecl.getName() + + " from container '" + typeDefinition.getTargetNamespace() + "#" + typeDefinition.getName() + + "'"; + NoSuchElementException nse = new NoSuchElementException(msg); + nse.initCause(e); + throw nse; } + schema.add(descriptor); } - + + // set substitution group for descriptors here + for (XSDElementDeclaration elemDecl : children) { + if (elemDecl.isElementDeclarationReference()) { + elemDecl = elemDecl.getResolvedElementDeclaration(); + } + PropertyDescriptor att = null; + for (PropertyDescriptor desc : schema) { + if (desc.getName().getLocalPart().equals(elemDecl.getName()) + && desc.getName().getNamespaceURI() + .equals(elemDecl.getTargetNamespace())) { + att = desc; + break; + } + } + setSubstitutionGroup(complexTypeDef, elemDecl, att, crs); + } attType = createComplexAttributeType(assignedName, schema, complexTypeDef, superType); } else { Class<?> binding = String.class; @@ -705,7 +717,7 @@ private AttributeType getType(String namespace, String name) { Name typeName = Types.typeName(namespace, name); - return getAttributeType(typeName, null, null); + return getAttributeType(typeName); } private void createFoundationTypes() { Modified: trunk/modules/extension/app-schema/app-schema/src/test/java/org/geotools/data/complex/config/EmfAppSchemaReaderTest.java =================================================================== --- trunk/modules/extension/app-schema/app-schema/src/test/java/org/geotools/data/complex/config/EmfAppSchemaReaderTest.java 2012-06-19 10:16:38 UTC (rev 38817) +++ trunk/modules/extension/app-schema/app-schema/src/test/java/org/geotools/data/complex/config/EmfAppSchemaReaderTest.java 2012-06-20 02:50:19 UTC (rev 38818) @@ -166,7 +166,7 @@ Assert.assertEquals(8, ((ComplexFeatureTypeImpl) wq_plus_Type).getTypeDescriptors().size()); Name name = Types.typeName(NS_URI, "wq_plus"); - AttributeDescriptor wqPlusDescriptor = typeRegistry.getDescriptor(name); + AttributeDescriptor wqPlusDescriptor = typeRegistry.getDescriptor(name, null, null, null); Assert.assertNotNull(wqPlusDescriptor); Assert.assertEquals(name, wqPlusDescriptor.getName()); Assert.assertSame(wq_plus_Type, wqPlusDescriptor.getType()); |
From: <svn...@os...> - 2012-06-19 10:16:49
|
Author: danieleromagnoli Date: 2012-06-19 03:16:38 -0700 (Tue, 19 Jun 2012) New Revision: 38817 Modified: branches/2.7.x/pom.xml Log: Updating to ImageIO-Ext 1.1.4 Modified: branches/2.7.x/pom.xml =================================================================== --- branches/2.7.x/pom.xml 2012-06-19 09:57:05 UTC (rev 38816) +++ branches/2.7.x/pom.xml 2012-06-19 10:16:38 UTC (rev 38817) @@ -73,7 +73,7 @@ <stress.skip.pattern>**/*StressTest.java</stress.skip.pattern> <test.maxHeapSize>512M</test.maxHeapSize> <src.output>${basedir}/target</src.output> - <imageio.ext.version>1.1.3</imageio.ext.version> + <imageio.ext.version>1.1.4</imageio.ext.version> <jt.version>1.1.1</jt.version> <jvm.opts></jvm.opts> <maven.build.timestamp.format>dd-MMM-yyyy HH:mm</maven.build.timestamp.format> |
From: <svn...@os...> - 2012-06-19 09:57:13
|
Author: danieleromagnoli Date: 2012-06-19 02:57:05 -0700 (Tue, 19 Jun 2012) New Revision: 38816 Modified: trunk/pom.xml Log: Updating to ImageIO-Ext 1.1.4 Modified: trunk/pom.xml =================================================================== --- trunk/pom.xml 2012-06-18 22:28:48 UTC (rev 38815) +++ trunk/pom.xml 2012-06-19 09:57:05 UTC (rev 38816) @@ -91,7 +91,7 @@ <test.exclude.pattern>disabled</test.exclude.pattern> <test.maxHeapSize>512M</test.maxHeapSize> <src.output>${basedir}/target</src.output> - <imageio.ext.version>1.1.3</imageio.ext.version> + <imageio.ext.version>1.1.4</imageio.ext.version> <jt.version>1.2.0</jt.version> <jvm.opts></jvm.opts> <maven.build.timestamp.format>dd-MMM-yyyy HH:mm</maven.build.timestamp.format> |
From: <svn...@os...> - 2012-06-18 22:28:56
|
Author: mdavis Date: 2012-06-18 15:28:48 -0700 (Mon, 18 Jun 2012) New Revision: 38815 Added: trunk/modules/unsupported/process-feature/src/main/java/org/geotools/process/feature/gs/PointStackerProcess.java trunk/modules/unsupported/process-feature/src/test/java/org/geotools/process/feature/gs/PointStackerProcessTest.java Log: Added PointStacker Rendering Transformation process Added: trunk/modules/unsupported/process-feature/src/main/java/org/geotools/process/feature/gs/PointStackerProcess.java =================================================================== --- trunk/modules/unsupported/process-feature/src/main/java/org/geotools/process/feature/gs/PointStackerProcess.java (rev 0) +++ trunk/modules/unsupported/process-feature/src/main/java/org/geotools/process/feature/gs/PointStackerProcess.java 2012-06-18 22:28:48 UTC (rev 38815) @@ -0,0 +1,384 @@ +/* + * GeoTools - The Open Source Java GIS Toolkit + * http://geotools.org + * + * (C) 2011, Open Source Geospatial Foundation (OSGeo) + * (C) 2001-2007 TOPP - www.openplans.org. + * + * 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.process.feature.gs; + +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import org.geotools.data.collection.ListFeatureCollection; +import org.geotools.data.simple.SimpleFeatureCollection; +import org.geotools.data.simple.SimpleFeatureIterator; +import org.geotools.feature.simple.SimpleFeatureBuilder; +import org.geotools.feature.simple.SimpleFeatureTypeBuilder; +import org.geotools.geometry.jts.ReferencedEnvelope; +import org.geotools.process.ProcessException; +import org.geotools.process.factory.DescribeParameter; +import org.geotools.process.factory.DescribeProcess; +import org.geotools.process.factory.DescribeResult; +import org.geotools.process.gs.GSProcess; +import org.geotools.referencing.CRS; +import org.opengis.feature.simple.SimpleFeature; +import org.opengis.feature.simple.SimpleFeatureType; +import org.opengis.referencing.FactoryException; +import org.opengis.referencing.crs.CoordinateReferenceSystem; +import org.opengis.referencing.operation.MathTransform; +import org.opengis.referencing.operation.TransformException; +import org.opengis.util.ProgressListener; + +import com.vividsolutions.jts.geom.Coordinate; +import com.vividsolutions.jts.geom.Geometry; +import com.vividsolutions.jts.geom.GeometryFactory; +import com.vividsolutions.jts.geom.Point; +import com.vividsolutions.jts.geom.impl.PackedCoordinateSequenceFactory; + +/** + * A Rendering Transformation process which aggregates features into a set of + * visually non-conflicting point features. + * The created points have attributes which provide the total number of points + * aggregated, as well as the number of unique point locations. + * <p> + * This is sometimes called "point clustering". + * The term stacking is used instead, since clustering has multiple + * meanings in geospatial processing - it is also used to + * mean identifying groups defined by point proximity. + * <p> + * The stacking is defined by specifying a grid to aggregate to. + * The grid cell size is specified in pixels relative to the requested output image size. + * This makes it more intuitive to pick an appropriate grid size, + * and ensures that the aggregation works at all zoom levels. + * <p> + * The output is a FeatureCollection containing the following attributes: + * <ul> + * <li><code>geom</code> - the point representing the cluster + * <li><code>count</code> - the total number of points in the cluster + * <li><code>countunique</code> - the number of unique point locations in the cluster + * </ul> + * Note that as required by the Rendering Transformation API, the output + * has the CRS of the input data. + * + * @author mdavis + * + */ +@DescribeProcess(title = "PointStacker", description = "Aggregates a collection of points into a set of stacked points.") +public class PointStackerProcess implements GSProcess { + + public static final String ATTR_GEOM = "geom"; + public static final String ATTR_COUNT = "count"; + public static final String ATTR_COUNT_UNIQUE = "countunique"; + + //TODO: add ability to pick index point selection strategy + //TODO: add ability to set attribute name containing value to be aggregated + //TODO: add ability to specify aggregation method (COUNT, SUM, AVG) + //TODO: ultimately could allow aggregating multiple input attributes, with different methods for each + //TODO: allow including attributes from input data (eg for use with points that are not aggregated) + //TODO: expand query window to avoid edge effects? + + // no process state is defined, since RenderingTransformation processes must be stateless + + @DescribeResult(name = "result", description = "The collection of stacked points") + public SimpleFeatureCollection execute( + + // process data + @DescribeParameter(name = "data", description = "Features containing the data points") SimpleFeatureCollection data, + + // process parameters + @DescribeParameter(name = "cellSize", description = "Cell size for gridding, in pixels") Integer cellSize, + + // output image parameters + @DescribeParameter(name = "outputBBOX", description = "Georeferenced bounding box of the output image") ReferencedEnvelope outputEnv, + @DescribeParameter(name = "outputWidth", description = "Width of the output image, in pixels") Integer outputWidth, + @DescribeParameter(name = "outputHeight", description = "Height of the output image, in pixels") Integer outputHeight, + + ProgressListener monitor) throws ProcessException, TransformException { + + CoordinateReferenceSystem srcCRS = data.getSchema().getCoordinateReferenceSystem(); + CoordinateReferenceSystem dstCRS = outputEnv.getCoordinateReferenceSystem(); + MathTransform crsTransform = null; + MathTransform invTransform = null; + try { + crsTransform = CRS.findMathTransform(srcCRS, dstCRS); + invTransform = crsTransform.inverse(); + } catch (FactoryException e) { + throw new ProcessException(e); + } + + // TODO: allow output CRS to be different to data CRS + // assume same CRS for now... + double cellSizeSrc = cellSize * outputEnv.getWidth() / outputWidth; + + Collection<StackedPoint> stackedPts = stackPoints(data, crsTransform, cellSizeSrc, + outputEnv.getMinX(), outputEnv.getMinY()); + + SimpleFeatureType schema = createType(srcCRS); + SimpleFeatureCollection result = new ListFeatureCollection(schema); + SimpleFeatureBuilder fb = new SimpleFeatureBuilder(schema); + + GeometryFactory factory = new GeometryFactory(new PackedCoordinateSequenceFactory()); + + double[] srcPt = new double[2]; + double[] dstPt = new double[2]; + + + for (StackedPoint sp : stackedPts) { + // create feature for stacked point + Coordinate pt = sp.getLocation(); + + // transform back to src CRS, since RT rendering expects the output to be in the same CRS + srcPt[0] = pt.x; + srcPt[1] = pt.y; + invTransform.transform(srcPt, 0, dstPt, 0, 1); + Coordinate psrc = new Coordinate(dstPt[0], dstPt[1]); + + Geometry point = factory.createPoint(psrc); + fb.add(point); + fb.add(sp.getCount()); + fb.add(sp.getCountUnique()); + + result.add(fb.buildFeature(null)); + } + return result; + } + + /** + * Computes the stacked points for the given data collection. + * All geometry types are handled - for non-point geometries, the centroid is used. + * + * @param data + * @param cellSize + * @param minX + * @param minY + * @return + * @throws TransformException + */ + private Collection<StackedPoint> stackPoints(SimpleFeatureCollection data, + MathTransform crsTransform, + double cellSize, double minX, double minY) throws TransformException { + SimpleFeatureIterator featureIt = data.features(); + + Map<Coordinate, StackedPoint> stackedPts = new HashMap<Coordinate, StackedPoint>(); + + double[] srcPt = new double[2]; + double[] dstPt = new double[2]; + + Coordinate indexPt = new Coordinate(); + try { + while (featureIt.hasNext()) { + SimpleFeature feature = featureIt.next(); + // get the point location from the geometry + Geometry geom = (Geometry) feature.getDefaultGeometry(); + Coordinate p = getRepresentativePoint(geom); + + // reproject data point to output CRS, if required + srcPt[0] = p.x; + srcPt[1] = p.y; + crsTransform.transform(srcPt, 0, dstPt, 0, 1); + Coordinate pout = new Coordinate(dstPt[0], dstPt[1]); + + indexPt.x = pout.x; + indexPt.y = pout.y; + gridIndex(indexPt, cellSize); + + StackedPoint stkPt = stackedPts.get(indexPt); + if (stkPt == null) { + + /** + * Note that the + */ + double centreX = indexPt.x * cellSize + cellSize / 2; + double centreY = indexPt.y * cellSize + cellSize / 2; + + stkPt = new StackedPoint(indexPt, new Coordinate(centreX, centreY)); + stackedPts.put(stkPt.getKey(), stkPt); + } + stkPt.add(pout); + } + + } finally { + featureIt.close(); + } + return stackedPts.values(); + } + + /** + * Gets a point to represent the Geometry. + * If the Geometry is a point, this is returned. + * Otherwise, the centroid is used. + * + * @param g the geometry to find a point for + * @return a point representing the Geometry + */ + private static Coordinate getRepresentativePoint(Geometry g) + { + if (g.getNumPoints() == 1) + return g.getCoordinate(); + return g.getCentroid().getCoordinate(); + } + + /** + * Computes the grid index for a point for the grid determined by the cellsize. + * + * @param griddedPt the point to grid, and also holds the output value + * @param cellSize the grid cell size + */ + private void gridIndex(Coordinate griddedPt, double cellSize) { + + // TODO: is there any situation where this could result in too much loss of precision? + /** + * The grid is based at the origin of the entire data space, + * not just the query window. + * This makes gridding stable during panning. + * + * This should not lose too much precision for any reasonable coordinate system and map size. + * The worst case is a CRS with small ordinate values, and a large cell size. + * The worst case tested is a map in degrees, zoomed out to show about twice the globe - works fine. + */ + // Use longs to avoid possible overflow issues (e.g. for a very small cell size) + long ix = (long) ((griddedPt.x) / cellSize); + long iy = (long) ((griddedPt.y) / cellSize); + + griddedPt.x = ix; + griddedPt.y = iy; + } + + private SimpleFeatureType createType(CoordinateReferenceSystem crs) { + SimpleFeatureTypeBuilder tb = new SimpleFeatureTypeBuilder(); + tb.add(ATTR_GEOM, Point.class, crs); + tb.add(ATTR_COUNT, Integer.class); + tb.add(ATTR_COUNT_UNIQUE, Integer.class); + tb.setName("stackedPoint"); + SimpleFeatureType sfType = tb.buildFeatureType(); + return sfType; + } + + private static class StackedPoint { + private Coordinate key; + + private Coordinate centerPt; + + private Coordinate location = null; + + private int count = 0; + + private Set<Coordinate> uniquePts; + + /** + * Creates a new stacked point grid cell. + * The center point of the cell is supplied + * so that it may be used as or influence the + * location of the final display point + * + * @param key a key for the grid cell (using integer ordinates to avoid precision issues) + * @param centerPt the center point of the grid cell + */ + public StackedPoint(Coordinate key, Coordinate centerPt) { + this.key = new Coordinate(key); + this.centerPt = centerPt; + } + + public Coordinate getKey() { + return key; + } + + public Coordinate getLocation() { + return location; + } + + public int getCount() { + return count; + } + + public int getCountUnique() { + if (uniquePts == null) + return 1; + return uniquePts.size(); + } + + public void add(Coordinate pt) { + count++; + /** + * Only create set if this is the second point seen + * (and assum the first pt is in location) + */ + if (uniquePts == null) { + uniquePts = new HashSet<Coordinate>(); + } + uniquePts.add(pt); + + pickNearestLocation(pt); + //pickCenterLocation(pt); + } + + /** + * Picks the location as the point + * which is nearest to the center of the cell. + * In addition, the nearest location is averaged with the cell center. + * This gives the best chance of avoiding conflicts. + * + * @param pt + */ + private void pickNearestLocation(Coordinate pt) { + // strategy - pick most central point + if (location == null) { + location = average(centerPt, pt); + return; + } + if (pt.distance(centerPt) < location.distance(centerPt)) { + location = average(centerPt, pt); + } + } + + /** + * Picks the location as the centre point of the cell. + * This does not give a good visualization - the gridding is very obvious + * + * @param pt + */ + private void pickCenterLocation(Coordinate pt) { + // strategy - pick first point + if (location == null) { + location = new Coordinate(pt); + return; + } + location = centerPt; + } + + /** + * Picks the first location encountered as the cell location. + * This is sub-optimal, since if the first point is near the cell + * boundary it is likely to collide with neighbouring points. + * + * @param pt + */ + private void pickFirstLocation(Coordinate pt) { + // strategy - pick first point + if (location == null) { + location = new Coordinate(pt); + } + } + + private static Coordinate average(Coordinate p1, Coordinate p2) + { + double x = (p1.x + p2.x) / 2; + double y = (p1.y + p2.y) / 2; + return new Coordinate(x, y); + } + } +} Added: trunk/modules/unsupported/process-feature/src/test/java/org/geotools/process/feature/gs/PointStackerProcessTest.java =================================================================== --- trunk/modules/unsupported/process-feature/src/test/java/org/geotools/process/feature/gs/PointStackerProcessTest.java (rev 0) +++ trunk/modules/unsupported/process-feature/src/test/java/org/geotools/process/feature/gs/PointStackerProcessTest.java 2012-06-18 22:28:48 UTC (rev 38815) @@ -0,0 +1,198 @@ +/* + * GeoTools - The Open Source Java GIS Toolkit + * http://geotools.org + * + * (C) 2002-2011, Open Source Geospatial Foundation (OSGeo) + * + * 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.process.feature.gs; + +import static junit.framework.Assert.*; + +import org.geotools.data.simple.SimpleFeatureCollection; +import org.geotools.data.simple.SimpleFeatureIterator; +import org.geotools.feature.FeatureCollections; +import org.geotools.feature.simple.SimpleFeatureBuilder; +import org.geotools.feature.simple.SimpleFeatureTypeBuilder; +import org.geotools.geometry.jts.ReferencedEnvelope; +import org.geotools.process.ProcessException; +import org.geotools.referencing.CRS; +import org.geotools.referencing.crs.DefaultGeographicCRS; +import org.junit.Test; +import org.opengis.feature.simple.SimpleFeature; +import org.opengis.feature.simple.SimpleFeatureType; +import org.opengis.referencing.FactoryException; +import org.opengis.referencing.NoSuchAuthorityCodeException; +import org.opengis.referencing.crs.CoordinateReferenceSystem; +import org.opengis.referencing.operation.TransformException; +import org.opengis.util.ProgressListener; + +import com.vividsolutions.jts.geom.Coordinate; +import com.vividsolutions.jts.geom.Geometry; +import com.vividsolutions.jts.geom.GeometryFactory; +import com.vividsolutions.jts.geom.MultiPoint; +import com.vividsolutions.jts.geom.Point; +import com.vividsolutions.jts.geom.impl.PackedCoordinateSequenceFactory; + +/** + * Unit test for PointStackerProcess. + * + * @author Martin Davis, OpenGeo + * + */ +public class PointStackerProcessTest { + @Test + public void testSimple() throws ProcessException, TransformException { + ReferencedEnvelope bounds = new ReferencedEnvelope(0, 10, 0, 10, DefaultGeographicCRS.WGS84); + + // Simple dataset with some coincident points + Coordinate[] data = new Coordinate[] { new Coordinate(4, 4), new Coordinate(4.1, 4.1), + new Coordinate(4.1, 4.1), new Coordinate(8, 8) }; + + + SimpleFeatureCollection fc = createPoints(data, bounds); + ProgressListener monitor = null; + + PointStackerProcess psp = new PointStackerProcess(); + SimpleFeatureCollection result = psp.execute(fc, 100, // cellSize + bounds, // outputBBOX + 1000, // outputWidth + 1000, // outputHeight + monitor); + + checkSchemaCorrect(result.getSchema()); + assertEquals(2, result.size()); + checkResultPoint(result, new Coordinate(4, 4), 3, 2); + checkResultPoint(result, new Coordinate(8, 8), 1, 1); + } + + /** + * Tests point stacking when output CRS is different to data CRS. + * The result data should be reprojected. + * + * @throws NoSuchAuthorityCodeException + * @throws FactoryException + * @throws TransformException + * @throws ProcessException + */ + @Test + public void testReprojected() throws NoSuchAuthorityCodeException, FactoryException, ProcessException, TransformException { + + ReferencedEnvelope inBounds = new ReferencedEnvelope(0, 10, 0, 10, DefaultGeographicCRS.WGS84); + + // Dataset with some points located in appropriate area + // points are close enough to create a single cluster + Coordinate[] data = new Coordinate[] { new Coordinate(-121.813201, 48.777343), new Coordinate(-121.813, 48.777) }; + + + SimpleFeatureCollection fc = createPoints(data, inBounds); + ProgressListener monitor = null; + + // Google Mercator BBOX for northern Washington State (roughly) + CoordinateReferenceSystem webMerc = CRS.decode("EPSG:3785"); + ReferencedEnvelope outBounds = new ReferencedEnvelope(-1.4045034049133E7, -1.2937920131607E7, 5916835.1504419, 6386464.2521607, webMerc); + + PointStackerProcess psp = new PointStackerProcess(); + SimpleFeatureCollection result = psp.execute(fc, 100, // cellSize + outBounds, // outputBBOX + 1810, // outputWidth + 768, // outputHeight + monitor); + + checkSchemaCorrect(result.getSchema()); + assertEquals(1, result.size()); + assertEquals(inBounds.getCoordinateReferenceSystem(), result.getBounds().getCoordinateReferenceSystem()); + checkResultPoint(result, new Coordinate(-121.813201, 48.777343), 2, 2); + } + + /** + * Check that a result set contains a stacked point in the right cell with expected attribute + * values. Because it's not known in advance what the actual location of a stacked point will + * be, a nearest-point strategy is used. + * + * @param result + * @param coordinate + * @param i + * @param j + */ + private void checkResultPoint(SimpleFeatureCollection result, Coordinate testPt, + int expectedCount, int expectedCountUnique) { + /** + * Find closest point to loc pt, then check that the attributes match + */ + double minDist = Double.MAX_VALUE; + int count = -1; + int countunique = -1; + + // find nearest result to testPt + for (SimpleFeatureIterator it = result.features(); it.hasNext();) { + SimpleFeature f = it.next(); + Coordinate outPt = ((Point) f.getDefaultGeometry()).getCoordinate(); + double dist = outPt.distance(testPt); + if (dist < minDist) { + minDist = dist; + count = (Integer) f.getAttribute(PointStackerProcess.ATTR_COUNT); + countunique = (Integer) f.getAttribute(PointStackerProcess.ATTR_COUNT_UNIQUE); + } + } + assertEquals(expectedCount, count); + assertEquals(expectedCountUnique, countunique); + } + + private void checkResultBounds(SimpleFeatureCollection result, ReferencedEnvelope bounds) { + boolean isInBounds = true; + for (SimpleFeatureIterator it = result.features(); it.hasNext();) { + SimpleFeature f = it.next(); + Coordinate outPt = ((Point) f.getDefaultGeometry()).getCoordinate(); + if (! bounds.contains(outPt)) { + isInBounds = false; + System.out.println("Found point out of bounds: " + f.getDefaultGeometry()); + } + } + assertTrue(isInBounds); + } + + private void checkSchemaCorrect(SimpleFeatureType ft) { + assertEquals(3, ft.getAttributeCount()); + assertEquals(Point.class, ft.getGeometryDescriptor().getType().getBinding()); + assertEquals(Integer.class, ft.getDescriptor(PointStackerProcess.ATTR_COUNT).getType() + .getBinding()); + assertEquals(Integer.class, ft.getDescriptor(PointStackerProcess.ATTR_COUNT_UNIQUE) + .getType().getBinding()); + + } + + private SimpleFeatureCollection createPoints(Coordinate[] pts, ReferencedEnvelope bounds) { + + SimpleFeatureTypeBuilder tb = new SimpleFeatureTypeBuilder(); + tb.setName("data"); + tb.setCRS(bounds.getCoordinateReferenceSystem()); + tb.add("shape", MultiPoint.class); + tb.add("value", Double.class); + + SimpleFeatureType type = tb.buildFeatureType(); + SimpleFeatureBuilder fb = new SimpleFeatureBuilder(type); + SimpleFeatureCollection fc = FeatureCollections.newCollection(); + + GeometryFactory factory = new GeometryFactory(new PackedCoordinateSequenceFactory()); + + for (Coordinate p : pts) { + Geometry point = factory.createPoint(p); + fb.add(point); + fb.add(p.z); + fc.add(fb.buildFeature(null)); + } + + return fc; + } + +} |
Author: mdavis Date: 2012-06-18 11:34:42 -0700 (Mon, 18 Jun 2012) New Revision: 38814 Added: trunk/modules/unsupported/process-raster/src/main/java/org/geotools/process/raster/surface/HeatmapProcess.java trunk/modules/unsupported/process-raster/src/main/java/org/geotools/process/raster/surface/HeatmapSurface.java trunk/modules/unsupported/process-raster/src/test/java/org/geotools/process/raster/surface/HeatmapProcessTest.java Log: Added Heatmap Rendering Transformation process Added: trunk/modules/unsupported/process-raster/src/main/java/org/geotools/process/raster/surface/HeatmapProcess.java =================================================================== --- trunk/modules/unsupported/process-raster/src/main/java/org/geotools/process/raster/surface/HeatmapProcess.java (rev 0) +++ trunk/modules/unsupported/process-raster/src/main/java/org/geotools/process/raster/surface/HeatmapProcess.java 2012-06-18 18:34:42 UTC (rev 38814) @@ -0,0 +1,403 @@ +/* + * GeoTools - The Open Source Java GIS Toolkit + * http://geotools.org + * + * (C) 2011, Open Source Geospatial Foundation (OSGeo) + * (C) 2008-2011 TOPP - www.openplans.org. + * + * 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.process.raster.surface; + +import javax.measure.unit.NonSI; +import javax.measure.unit.SI; +import javax.measure.unit.Unit; + +import org.geotools.coverage.CoverageFactoryFinder; +import org.geotools.coverage.grid.GridCoverage2D; +import org.geotools.coverage.grid.GridCoverageFactory; +import org.geotools.data.Query; +import org.geotools.data.simple.SimpleFeatureCollection; +import org.geotools.data.simple.SimpleFeatureIterator; +import org.geotools.factory.Hints; +import org.geotools.filter.text.cql2.CQLException; +import org.geotools.filter.text.ecql.ECQL; +import org.geotools.geometry.jts.ReferencedEnvelope; +import org.geotools.process.ProcessException; +import org.geotools.process.factory.DescribeParameter; +import org.geotools.process.factory.DescribeProcess; +import org.geotools.process.factory.DescribeResult; +import org.geotools.process.gs.GSProcess; +import org.geotools.referencing.CRS; +import org.opengis.coverage.grid.GridCoverage; +import org.opengis.coverage.grid.GridGeometry; +import org.opengis.feature.simple.SimpleFeature; +import org.opengis.filter.Filter; +import org.opengis.filter.expression.Expression; +import org.opengis.referencing.FactoryException; +import org.opengis.referencing.crs.CoordinateReferenceSystem; +import org.opengis.referencing.operation.MathTransform; +import org.opengis.util.ProgressListener; + +import com.vividsolutions.jts.geom.Coordinate; +import com.vividsolutions.jts.geom.Geometry; + +/** + * A Process that uses a {@link HeatmapSurface} to compute a heatmap surface over a set of + * irregular data points as a {@link GridCoverage}. + * Heatmaps are known more formally as <i>Multivariate Kernel Density Estimation</i>. + * <p> + * The appearance of the heatmap is controlled by the kernel radius, + * which determines the "radius of influence" of input points. + * The radius is specified by the radiusPixels parameter, + * which is in output pixels. + * Using pixels allows easy estimation of a value which will give a visually + * effective result, + * and ensures the heatmap appearance changes to match the zoom level. + * <p> + * By default each input point has weight 1. + * Optionally the weights of points may be supplied by an attribute specified by the <code>weightAttr</code> parameter. + * <p> + * All geometry types are allowed as input. For non-point geometries the centroid is used. + * <p> + * To improve performance, the surface grid can be computed at a lower resolution than the requested + * output image using the <code>pixelsPerCell</code> parameter. + * The grid is upsampled to match the required image size. Upsampling uses Bilinear + * Interpolation to maintain visual quality. This gives a large improvement in performance, with + * minimal impact on visual quality for small cell sizes (for instance, 10 pixels or less). + * <p> + * To ensure that the computed surface is stable (i.e. does not display obvious edge artifacts under + * zooming and panning), the data extent is expanded to be larger than the specified output + * extent. The expansion distance is equal to the size of <code>radiusPixels</code> in the input + * CRS. + * + * <h3>Parameters</h3> <i>M = mandatory, O = optional</i> + * <ul> + * <li><b>data</b> (M) - the FeatureCollection containing the point observations + * <li><b>radiusPixels</b> (M)- the density kernel radius, in pixels + * <li><b>weightAttr</b> (M)- the feature type attribute containing the observed surface value + * <li><b>pixelsPerCell</b> (O) - The pixels-per-cell value determines the resolution of the + * computed grid. Larger values improve performance, but degrade appearance. (Default = 1) + * <li><b>outputBBOX</b> (M) - The georeferenced bounding box of the output area + * <li><b>outputWidth</b> (M) - The width of the output raster + * <li><b>outputHeight</b> (M) - The height of the output raster + * </ul> + * The output of the process is a {@linkplain GridCoverage2D} with a single band, with cell values + * in the range [0, 1]. + * <p> + * Computation of the surface takes places in the CRS of the output. + * If the data CRS is different to the output CRS, the input points are transformed into the output CRS. + * + * <h3>Using the process as a Rendering Transformation</h3> + * + * This process can be used as a RenderingTransformation, since it implements the + * <tt>invertQuery(... Query, GridGeometry)</tt> method. In this case the <code>queryBuffer</code> + * parameter should be specified to expand the query extent appropriately. The output raster + * parameters may be provided from the request extents, using the following SLD environment + * variables: + * <ul> + * <li><b>outputBBOX</b> - env var = <tt>wms_bbox</tt> + * <li><b>outputWidth</b> - env var = <tt>wms_width</tt> + * <li><b>outputHeight</b> - env var = <tt>wms_height</tt> + * </ul> + * When used as an Rendering Transformation the data query is rewritten to expand the query BBOX, to + * ensure that enough data points are queried to make the computed surface stable under panning and + * zooming. + * + * <p> + * + * @author Martin Davis - OpenGeo + * + */ +@DescribeProcess(title = "Heatmap", description = "Computes a heatmap surface over a set of irregular data points as a GridCoverage.") +public class HeatmapProcess implements GSProcess { + + @DescribeResult(name = "result", description = "The heat map surface as a raster") + public GridCoverage2D execute( + + // process data + @DescribeParameter(name = "data", description = "Features containing the data points") SimpleFeatureCollection obsFeatures, + + // process parameters + @DescribeParameter(name = "radiusPixels", description = "Radius to use for the kernel, in pixels") Integer argRadiusPixels, + @DescribeParameter(name = "weightAttr", description = "Featuretype attribute containing the point weight value", min = 0, max = 1) String valueAttr, + @DescribeParameter(name = "pixelsPerCell", description = "Number of pixels per grid cell (default = 1)", min = 0, max = 1) Integer argPixelsPerCell, + + // output image parameters + @DescribeParameter(name = "outputBBOX", description = "Georeferenced bounding box of the output") ReferencedEnvelope argOutputEnv, + @DescribeParameter(name = "outputWidth", description = "Width of the output raster") Integer argOutputWidth, + @DescribeParameter(name = "outputHeight", description = "Height of the output raster") Integer argOutputHeight, + + ProgressListener monitor) throws ProcessException { + + /** + * -------- Extract required information from process arguments ------------- + */ + int pixelsPerCell = 1; + if (argPixelsPerCell != null && argPixelsPerCell > 1) { + pixelsPerCell = argPixelsPerCell; + } + int outputWidth = argOutputWidth; + int outputHeight = argOutputHeight; + int gridWidth = outputWidth; + int gridHeight = outputHeight; + if (pixelsPerCell > 1) { + gridWidth = outputWidth / pixelsPerCell; + gridHeight = outputHeight / pixelsPerCell; + } + + /** + * Compute transform to convert input coords into output CRS + */ + CoordinateReferenceSystem srcCRS = obsFeatures.getSchema().getCoordinateReferenceSystem(); + CoordinateReferenceSystem dstCRS = argOutputEnv.getCoordinateReferenceSystem(); + MathTransform trans = null; + try { + trans = CRS.findMathTransform(srcCRS, dstCRS); + } catch (FactoryException e) { + throw new ProcessException(e); + } + + //------------ Kernel Radius + /* + * // not used for now - only pixel radius values are supported double + * distanceConversionFactor = distanceConversionFactor(srcCRS, dstCRS); double dstRadius = + * argRadius * distanceConversionFactor; + */ + int radiusCells = 100; + if (argRadiusPixels != null) + radiusCells = argRadiusPixels; + if (pixelsPerCell > 1) { + radiusCells /= pixelsPerCell; + } + + + /** + * -------------- Extract the input observation points ----------- + */ + HeatmapSurface heatMap = new HeatmapSurface(radiusCells, argOutputEnv, gridWidth, + gridHeight); + try { + extractPoints(obsFeatures, valueAttr, trans, heatMap); + } catch (CQLException e) { + throw new ProcessException(e); + } + + /** + * --------------- Do the processing ------------------------------ + */ + // Stopwatch sw = new Stopwatch(); + // compute the heatmap at the specified resolution + float[][] heatMapGrid = heatMap.computeSurface(); + + // flip now, since grid size may be smaller + heatMapGrid = flipXY(heatMapGrid); + + // upsample to output resolution if necessary + float[][] outGrid = heatMapGrid; + if (pixelsPerCell > 1) + outGrid = upsample(heatMapGrid, -999, outputWidth, outputHeight); + + // convert to the GridCoverage2D required for output + GridCoverageFactory gcf = CoverageFactoryFinder.getGridCoverageFactory(null); + GridCoverage2D gridCov = gcf.create("Process Results", outGrid, argOutputEnv); + + // System.out.println("************** Heatmap computed in " + sw.getTimeString()); + + return gridCov; + } + + /* + * An approximate value for the length of a degree at the equator in meters. This doesn't have + * to be precise, since it is only used to convert values which are themselves rough + * approximations. + */ + private static final double METRES_PER_DEGREE = 111320; + + private static double distanceConversionFactor(CoordinateReferenceSystem srcCRS, + CoordinateReferenceSystem dstCRS) { + Unit<?> srcUnit = srcCRS.getCoordinateSystem().getAxis(0).getUnit(); + Unit<?> dstUnit = dstCRS.getCoordinateSystem().getAxis(0).getUnit(); + if (srcUnit == dstUnit) { + return 1; + } else if (srcUnit == NonSI.DEGREE_ANGLE && dstUnit == SI.METER) { + return METRES_PER_DEGREE; + } else if (srcUnit == SI.METER && dstUnit == NonSI.DEGREE_ANGLE) { + return 1.0 / METRES_PER_DEGREE; + } + throw new IllegalStateException("Unable to convert distances from " + srcUnit + " to " + + dstUnit); + } + + /** + * Flips an XY matrix along the X=Y axis, and inverts the Y axis. Used to convert from + * "map orientation" into the "image orientation" used by GridCoverageFactory. The surface + * interpolation is done on an XY grid, with Y=0 being the bottom of the space. GridCoverages + * are stored in an image format, in a YX grid with Y=0 being the top. + * + * @param grid the grid to flip + * @return the flipped grid + */ + private float[][] flipXY(float[][] grid) { + int xsize = grid.length; + int ysize = grid[0].length; + + float[][] grid2 = new float[ysize][xsize]; + for (int ix = 0; ix < xsize; ix++) { + for (int iy = 0; iy < ysize; iy++) { + int iy2 = ysize - iy - 1; + grid2[iy2][ix] = grid[ix][iy]; + } + } + return grid2; + } + + private float[][] upsample(float[][] grid, float noDataValue, int width, int height) { + BilinearInterpolator bi = new BilinearInterpolator(grid, noDataValue); + float[][] outGrid = bi.interpolate(width, height, false); + return outGrid; + } + + /** + * Given a target query and a target grid geometry returns the query to be used to read the + * input data of the process involved in rendering. In this process this method is used to: + * <ul> + * <li>determine the extent & CRS of the output grid + * <li>expand the query envelope to ensure stable surface generation + * <li>modify the query hints to ensure point features are returned + * </ul> + * Note that in order to pass validation, all parameters named here must also appear in the + * parameter list of the <tt>execute</tt> method, even if they are not used there. + * + * @param argRadiusPixels the feature type attribute that contains the observed surface value + * @param targetQuery the query used against the data source + * @param targetGridGeometry the grid geometry of the destination image + * @return The transformed query + */ + public Query invertQuery( + @DescribeParameter(name = "radiusPixels", description = "Radius to use for the kernel", min = 0, max = 1) Integer argRadiusPixels, + // output image parameters + @DescribeParameter(name = "outputBBOX", description = "Georeferenced bounding box of the output") ReferencedEnvelope argOutputEnv, + @DescribeParameter(name = "outputWidth", description = "Width of the output raster") Integer argOutputWidth, + @DescribeParameter(name = "outputHeight", description = "Height of the output raster") Integer argOutputHeight, + + Query targetQuery, GridGeometry targetGridGeometry) throws ProcessException { + + // TODO: handle different CRSes in input and output + + int radiusPixels = argRadiusPixels > 0 ? argRadiusPixels : 0; + // input parameters are required, so should be non-null + double queryBuffer = radiusPixels / pixelSize(argOutputEnv, argOutputWidth, argOutputHeight); + /* + * if (argQueryBuffer != null) { queryBuffer = argQueryBuffer; } + */ + targetQuery.setFilter(expandBBox(targetQuery.getFilter(), queryBuffer)); + + // clear properties to force all attributes to be read + // (required because the SLD processor cannot see the value attribute specified in the + // transformation) + // TODO: set the properties to read only the specified value attribute + targetQuery.setProperties(null); + + // set the decimation hint to ensure points are read + Hints hints = targetQuery.getHints(); + hints.put(Hints.GEOMETRY_DISTANCE, 0.0); + + return targetQuery; + } + + private double pixelSize(ReferencedEnvelope outputEnv, int outputWidth, int outputHeight) + { + // error-proofing + if (outputEnv.getWidth() <= 0) return 0; + // assume view is isotropic + return outputWidth / outputEnv.getWidth(); + } + + private Filter expandBBox(Filter filter, double distance) { + return (Filter) filter.accept(new BBOXExpandingFilterVisitor(distance, distance, distance, + distance), null); + } + + public static void extractPoints(SimpleFeatureCollection obsPoints, String attrName, + MathTransform trans, HeatmapSurface heatMap) throws CQLException { + Expression attrExpr = null; + if (attrName != null) { + attrExpr = ECQL.toExpression(attrName); + } + + SimpleFeatureIterator obsIt = obsPoints.features(); + + double[] srcPt = new double[2]; + double[] dstPt = new double[2]; + + int i = 0; + try { + while (obsIt.hasNext()) { + SimpleFeature feature = obsIt.next(); + + try { + // get the weight value, if any + double val = 1; + if (attrExpr != null) { + val = getPointValue(feature, attrExpr); + } + + // get the point location from the geometry + Geometry geom = (Geometry) feature.getDefaultGeometry(); + Coordinate p = getPoint(geom); + srcPt[0] = p.x; + srcPt[1] = p.y; + trans.transform(srcPt, 0, dstPt, 0, 1); + Coordinate pobs = new Coordinate(dstPt[0], dstPt[1], val); + + heatMap.addPoint(pobs.x, pobs.y, val); + } catch (Exception e) { + // just carry on for now (debugging) + // throw new ProcessException("Expression " + attrExpr + + // " failed to evaluate to a numeric value", e); + } + } + } finally { + obsIt.close(); + } + } + + /** + * Gets a point to represent the Geometry. If the Geometry is a point, this is returned. + * Otherwise, the centroid is used. + * + * @param g the geometry to find a point for + * @return a point representing the Geometry + */ + private static Coordinate getPoint(Geometry g) { + if (g.getNumPoints() == 1) + return g.getCoordinate(); + return g.getCentroid().getCoordinate(); + } + + /** + * Gets the value for a point from the supplied attribute. + * The value is checked for validity, + * and a default of 1 is used if necessary. + * + * @param feature the feature to extract the value from + * @param attrExpr the expression specifying the attribute to read + * @return the value for the point + */ + private static double getPointValue(SimpleFeature feature, Expression attrExpr) { + Double valObj = attrExpr.evaluate(feature, Double.class); + if (valObj != null) { + return valObj; + } + return 1; + } +} Added: trunk/modules/unsupported/process-raster/src/main/java/org/geotools/process/raster/surface/HeatmapSurface.java =================================================================== --- trunk/modules/unsupported/process-raster/src/main/java/org/geotools/process/raster/surface/HeatmapSurface.java (rev 0) +++ trunk/modules/unsupported/process-raster/src/main/java/org/geotools/process/raster/surface/HeatmapSurface.java 2012-06-18 18:34:42 UTC (rev 38814) @@ -0,0 +1,266 @@ +/* + * GeoTools - The Open Source Java GIS Toolkit + * http://geotools.org + * + * (C) 2011, Open Source Geospatial Foundation (OSGeo) + * (C) 2008-2011 TOPP - www.openplans.org. + * + * 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.process.raster.surface; + +import com.vividsolutions.jts.geom.Envelope; + +/** + * Computes a Heat Map surface from a set of irregular data points, each containing a positive + * height value. The nature of the surface is determined by a kernelRadius value, which indicates + * how far out each data points "spreads". + * <p> + * The Heatmap surface is computed as a grid (raster) of values representing the surface. For + * stability, the compute grid is expanded by the kernel radius on all four sides. This avoids + * "edge effects" from distorting the surface within the requested envelope. + * <p> + * The values in the output surface are normalized to lie in the range [0, 1]. + * + * @author Martin Davis, OpenGeo + * + */ +public class HeatmapSurface { + /** + * Number of iterations of box blur to approximate a Gaussian blur + */ + private static final int GAUSSIAN_APPROX_ITER = 4; + + private Envelope srcEnv; + + private int xSize; + + private int ySize; + + private GridTransform gridTrans; + + private float[][] grid; + + private int kernelRadiusGrid; + + /** + * Creates a new heatmap surface. + * + * @param kernelRadius the kernel radius, in grid units + * @param srcEnv the envelope defining the data space + * @param xSize the width of the output grid + * @param ySize the height of the output grid + */ + public HeatmapSurface(int kernelRadius, Envelope srcEnv, int xSize, int ySize) { + // radius must be non-negative + this.kernelRadiusGrid = Math.max(kernelRadius, 0); + + this.srcEnv = srcEnv; + this.xSize = xSize; + this.ySize = ySize; + + init(); + } + + private void init() { + gridTrans = new GridTransform(srcEnv, xSize, ySize); + + int xSizeExp = xSize + 2 * kernelRadiusGrid; + int ySizeExp = ySize + 2 * kernelRadiusGrid; + + grid = new float[xSizeExp][ySizeExp]; + } + + /** + * Adds a new data point to the surface. Data points can be coincident. + * + * @param x the X ordinate of the point + * @param y the Y ordinate of the point + * @param value the data value of the point + */ + public void addPoint(double x, double y, double value) + { + /** + * Input points are converted to grid space, and offset by the grid expansion offset + */ + int gi = gridTrans.i(x) + kernelRadiusGrid; + int gj = gridTrans.j(y) + kernelRadiusGrid; + + // check if point falls outside grid - skip it if so + if (gi < 0 || gi > grid.length || gj < 0 || gj > grid[0].length) + return; + + grid[gi][gj] += value; + // System.out.println("data[" + gi + ", " + gj + "] <- " + value); + } + + /** + * Computes a grid representing the heatmap surface. The grid is structured as an XY matrix, + * with (0,0) being the bottom left corner of the data space + * + * @return a grid representing the surface + */ + public float[][] computeSurface() { + + computeHeatmap(grid, kernelRadiusGrid); + + float[][] gridOut = extractGrid(grid, kernelRadiusGrid, kernelRadiusGrid, xSize, ySize); + + return gridOut; + } + + private float[][] extractGrid(float[][] grid, int xBase, int yBase, int xSize, int ySize) { + float[][] gridExtract = new float[xSize][ySize]; + for (int i = 0; i < xSize; i++) { + for (int j = 0; j < ySize; j++) { + gridExtract[i][j] = grid[xBase + i][yBase + j]; + } + } + return gridExtract; + } + + private float[][] computeHeatmap(float[][] grid, int kernelRadius) { + int xSize = grid.length; + int ySize = grid[0].length; + + int baseBoxKernelRadius = kernelRadius / GAUSSIAN_APPROX_ITER; + int radiusIncBreak = kernelRadius - baseBoxKernelRadius * GAUSSIAN_APPROX_ITER; + + /** + * Since Box Blur is linearly separable, can implement it by doing 2 1-D box blurs in + * different directions. Using a flipped buffer grid allows the same code to compute each + * direction, as well as preserving input grid values. + */ + // holds flipped copy of first box blur pass + float[][] grid2 = new float[ySize][xSize]; + for (int count = 0; count < GAUSSIAN_APPROX_ITER; count++) { + int boxKernelRadius = baseBoxKernelRadius; + /** + * If required, increment radius to ensure sum of radii equals total kernel radius + */ + if (count < radiusIncBreak) + boxKernelRadius++; + // System.out.println(boxKernelRadius); + + boxBlur(boxKernelRadius, grid, grid2); + boxBlur(boxKernelRadius, grid2, grid); + } + + // testNormalizeFactor(baseBoxKernelRadius, radiusIncBreak); + normalize(grid); + return grid; + } + + /** + * DON'T USE This method is too simplistic to determine normalization factor. Would need to use + * a full 2D grid and smooth it to get correct value + * + * @param baseBoxKernelRadius + * @param radiusIncBreak + */ + private void testNormalizeFactor(int baseBoxKernelRadius, int radiusIncBreak) { + double val = 1.0; + for (int count = 0; count < GAUSSIAN_APPROX_ITER; count++) { + int boxKernelRadius = baseBoxKernelRadius; + /** + * If required, increment radius to ensure sum of radii equals total kernel radius + */ + if (count < radiusIncBreak) + boxKernelRadius++; + + int dia = 2 * boxKernelRadius + 1; + float kernelVal = kernelVal(boxKernelRadius); + System.out.println(boxKernelRadius + " kernel val = " + kernelVal); + + if (count == 0) { + val = val * 1 * kernelVal; + } else { + val = val * dia * kernelVal; + } + System.out.println("norm val = " + val); + if (count == 0) { + val = val * 1 * kernelVal; + } else { + val = val * dia * kernelVal; + } + } + System.out.println("norm factor = " + val); + } + + /** + * Normalizes grid values to range [0,1] + * + * @param grid + */ + private void normalize(float[][] grid) { + float max = Float.NEGATIVE_INFINITY; + for (int i = 0; i < grid.length; i++) { + for (int j = 0; j < grid[0].length; j++) { + if (grid[i][j] > max) + max = grid[i][j]; + } + } + + float normFactor = 1.0f / max; + + for (int i = 0; i < grid.length; i++) { + for (int j = 0; j < grid[0].length; j++) { + grid[i][j] *= normFactor; + } + } + } + + private float kernelVal(int kernelRadius) { + // This kernel function has been confirmed to integrate to 1 over the full radius + float val = (float) (1.0f / (2 * kernelRadius + 1)); + return val; + } + + private void boxBlur(int kernelRadius, float[][] input, float[][] output) { + int width = input.length; + int height = input[0].length; + + // init moving average total + float kernelVal = kernelVal(kernelRadius); + // System.out.println("boxblur: radius = " + kernelRadius + " kernel val = " + kernelVal); + + for (int j = 0; j < height; j++) { + + double tot = 0.0; + + for (int i = -kernelRadius; i <= kernelRadius; i++) { + if (i < 0 || i >= width) + continue; + tot += kernelVal * input[i][j]; + } + + // System.out.println(tot); + + output[j][0] = (float) tot; + + for (int i = 1; i < width; i++) { + + // update box running total + int iprev = i - 1 - kernelRadius; + if (iprev >= 0) + tot -= kernelVal * input[iprev][j]; + + int inext = i + kernelRadius; + if (inext < width) + tot += kernelVal * input[inext][j]; + + output[j][i] = (float) tot; + // if (i==49 && j==147) System.out.println("val[ " + i + ", " + j + "] = " + tot); + + } + } + } +} Added: trunk/modules/unsupported/process-raster/src/test/java/org/geotools/process/raster/surface/HeatmapProcessTest.java =================================================================== --- trunk/modules/unsupported/process-raster/src/test/java/org/geotools/process/raster/surface/HeatmapProcessTest.java (rev 0) +++ trunk/modules/unsupported/process-raster/src/test/java/org/geotools/process/raster/surface/HeatmapProcessTest.java 2012-06-18 18:34:42 UTC (rev 38814) @@ -0,0 +1,127 @@ +/* + * GeoTools - The Open Source Java GIS Toolkit + * http://geotools.org + * + * (C) 2002-2011, Open Source Geospatial Foundation (OSGeo) + * + * 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.process.raster.surface; + +import static org.junit.Assert.assertTrue; + +import java.awt.geom.Point2D; + +import org.geotools.coverage.grid.GridCoverage2D; +import org.geotools.data.simple.SimpleFeatureCollection; +import org.geotools.feature.FeatureCollections; +import org.geotools.feature.simple.SimpleFeatureBuilder; +import org.geotools.feature.simple.SimpleFeatureTypeBuilder; +import org.geotools.geometry.jts.ReferencedEnvelope; +import org.geotools.referencing.crs.DefaultGeographicCRS; +import org.junit.Test; +import org.opengis.feature.simple.SimpleFeatureType; +import org.opengis.util.ProgressListener; + +import com.vividsolutions.jts.geom.Coordinate; +import com.vividsolutions.jts.geom.Geometry; +import com.vividsolutions.jts.geom.GeometryFactory; +import com.vividsolutions.jts.geom.MultiPoint; +import com.vividsolutions.jts.geom.impl.PackedCoordinateSequenceFactory; + +/** + * @author Martin Davis - OpenGeo + * + */ +public class HeatmapProcessTest { + + /** + * A test of a simple surface, validating that the process + * can be invoked and return a reasonable result in a simple situation. + * + * @throws Exception + */ + @Test + public void testSimpleSurface() { + + ReferencedEnvelope bounds = new ReferencedEnvelope(0, 10, 0, 10, DefaultGeographicCRS.WGS84); + Coordinate[] data = new Coordinate[] { + new Coordinate(4, 4), + new Coordinate(4, 6) + }; + SimpleFeatureCollection fc = createPoints(data, bounds); + + ProgressListener monitor = null; + + HeatmapProcess process = new HeatmapProcess(); + GridCoverage2D cov = process.execute(fc, // data + 20, //radius + null, // weightAttr + 1, // pixelsPerCell + bounds, // outputEnv + 100, // outputWidth + 100, // outputHeight + monitor // monitor) + ); + + // following tests are checking for an appropriate shape for the surface + + float center1 = coverageValue(cov, 4, 4); + float center2 = coverageValue(cov, 4, 6); + float midway = coverageValue(cov, 4, 5); + float far = coverageValue(cov, 9, 9); + + // peaks are roughly equal + float peakDiff = Math.abs(center1 - center2); + assert(peakDiff < center1 / 10); + + // dip between peaks + assertTrue(midway > center1 / 2); + + // surface is flat far away + assertTrue(far < center1 / 1000); + + } + + private float coverageValue(GridCoverage2D cov, double x, double y) + { + float[] covVal = new float[1]; + Point2D worldPos = new Point2D.Double(x, y); + cov.evaluate(worldPos, covVal); + return covVal[0]; + } + + private SimpleFeatureCollection createPoints(Coordinate[] pts, ReferencedEnvelope bounds) + { + + SimpleFeatureTypeBuilder tb = new SimpleFeatureTypeBuilder(); + tb.setName("data"); + tb.setCRS(bounds.getCoordinateReferenceSystem()); + tb.add("shape", MultiPoint.class); + tb.add("value", Double.class); + + SimpleFeatureType type = tb.buildFeatureType(); + SimpleFeatureBuilder fb = new SimpleFeatureBuilder(type); + SimpleFeatureCollection fc = FeatureCollections.newCollection(); + + GeometryFactory factory = new GeometryFactory(new PackedCoordinateSequenceFactory()); + + for (Coordinate p : pts) { + Geometry point = factory.createPoint(p); + fb.add(point); + fb.add(p.z); + fc.add(fb.buildFeature(null)); + } + + return fc; + } + +} |
Author: mdavis Date: 2012-06-18 11:28:10 -0700 (Mon, 18 Jun 2012) New Revision: 38813 Added: trunk/modules/unsupported/process-raster/src/main/java/org/geotools/process/raster/surface/ trunk/modules/unsupported/process-raster/src/main/java/org/geotools/process/raster/surface/BBOXExpandingFilterVisitor.java trunk/modules/unsupported/process-raster/src/main/java/org/geotools/process/raster/surface/BarnesSurfaceInterpolator.java trunk/modules/unsupported/process-raster/src/main/java/org/geotools/process/raster/surface/BarnesSurfaceProcess.java trunk/modules/unsupported/process-raster/src/main/java/org/geotools/process/raster/surface/BilinearInterpolator.java trunk/modules/unsupported/process-raster/src/main/java/org/geotools/process/raster/surface/GridTransform.java trunk/modules/unsupported/process-raster/src/test/java/org/geotools/process/raster/surface/ trunk/modules/unsupported/process-raster/src/test/java/org/geotools/process/raster/surface/BarnesSurfaceProcessTest.java trunk/modules/unsupported/process-raster/src/test/java/org/geotools/process/raster/surface/BilinearInterpolatorTest.java Log: Added BarnesSurfaceProcess and supporting classes Added: trunk/modules/unsupported/process-raster/src/main/java/org/geotools/process/raster/surface/BBOXExpandingFilterVisitor.java =================================================================== --- trunk/modules/unsupported/process-raster/src/main/java/org/geotools/process/raster/surface/BBOXExpandingFilterVisitor.java (rev 0) +++ trunk/modules/unsupported/process-raster/src/main/java/org/geotools/process/raster/surface/BBOXExpandingFilterVisitor.java 2012-06-18 18:28:10 UTC (rev 38813) @@ -0,0 +1,81 @@ +/* + * GeoTools - The Open Source Java GIS Toolkit + * http://geotools.org + * + * (C) 2011, Open Source Geospatial Foundation (OSGeo) + * (C) 2008-2011 TOPP - www.openplans.org. + * + * 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.process.raster.surface; + +import org.geotools.filter.visitor.DuplicatingFilterVisitor; +import org.opengis.filter.expression.Expression; +import org.opengis.filter.spatial.BBOX; + +/** + * A {@link DuplicatingFilterVisitor} which expands the {@link BBOX} of the filter + * by given distances for each box edge. + * + * @author Martin Davis - OpenGeo + * + */ +class BBOXExpandingFilterVisitor extends DuplicatingFilterVisitor { + private double expandMinX; + + private double expandMaxX; + + private double expandMinY; + + private double expandMaxY; + + /** + * Creates a new expanding filter. + * + * @param expandMinX the distance to expand the box X dimension leftwards + * @param expandMaxX the distance to expand the box X dimension rightwards + * @param expandMinY the distance to expand the box Y dimension downwards + * @param expandMaxY the distance to expand the box Y dimension upwards + */ + public BBOXExpandingFilterVisitor(double expandMinX, double expandMaxX, double expandMinY, + double expandMaxY) { + this.expandMinX = expandMinX; + this.expandMaxX = expandMaxX; + this.expandMinY = expandMinY; + this.expandMaxY = expandMaxX; + } + + /** + * Expands the BBOX in the Filter. + * + */ + @SuppressWarnings("deprecation") + @Override + public Object visit(BBOX filter, Object extraData) { + // no need to change the property name + Expression propertyName = filter.getExpression1(); + + /** + * Using the deprecated methods since they are too useful... + */ + double minx = filter.getMinX(); + double miny = filter.getMinY(); + double maxx = filter.getMaxX(); + double maxy = filter.getMaxY(); + String srs = filter.getSRS(); + + return getFactory(extraData).bbox(propertyName, + minx - expandMinX, miny - expandMaxX, + maxx + expandMinY, maxy + expandMaxY, + srs); + } + +} \ No newline at end of file Added: trunk/modules/unsupported/process-raster/src/main/java/org/geotools/process/raster/surface/BarnesSurfaceInterpolator.java =================================================================== --- trunk/modules/unsupported/process-raster/src/main/java/org/geotools/process/raster/surface/BarnesSurfaceInterpolator.java (rev 0) +++ trunk/modules/unsupported/process-raster/src/main/java/org/geotools/process/raster/surface/BarnesSurfaceInterpolator.java 2012-06-18 18:28:10 UTC (rev 38813) @@ -0,0 +1,472 @@ +/* + * GeoTools - The Open Source Java GIS Toolkit + * http://geotools.org + * + * (C) 2011, Open Source Geospatial Foundation (OSGeo) + * (C) 2008-2011 TOPP - www.openplans.org. + * + * 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.process.raster.surface; + +import com.vividsolutions.jts.geom.Coordinate; +import com.vividsolutions.jts.geom.Envelope; + +/** + * Interpolates a surface across a regular grid from an irregular set of data points + * using the Barnes Surface Interpolation technique. + * <p> + * Barnes Surface Interpolation is a surface estimating method + * commonly used as an interpolation technique for meteorological datasets. + * The algorithm operates on a regular grid of cells covering a specified extent in the input data space. + * It computes an initial pass to produce an averaged (smoothed) value for each cell in the grid, + * based on the cell's proximity to the points in the input observations. + * Subsequent refinement passes may be performed to improve the surface estimate + * to better approximate the observed values. + * <ul> + * <li>The initial pass produces an averaged (smoothed) value for each grid cell + * using a summation of exponential (Gaussian) decay functions around each observation point. + * <li>Subsequent refinement passes compute an error surface in the same way, using the deltas + * between the previous estimated surface and the observations. + * The error surface is added to the previous estimated surface to refine the estimate + * (by reducing the delta between the estimate and the observations). + * </ul> + * <p> + * For the first pass, the estimated value at each grid cell is: + * + * <pre> + * E<sub>g</sub> = sum(w<sub>i</sub> * o<sub>i</sub>) / sum(w<sub>i</sub>) + * </pre> + * + * where + * <ul> + * <li><code>E<sub>g</sub></code> is the estimated surface value at the grid cell + * <li><code>w<sub>i</sub></code> is the weight value for the i'th observation point (see below for definition) + * <li><code>o<sub>i</sub></code> is the value of the i'th observation point + * </ul> + * <p> + * The weight (decay) function used is: + * + * <pre> + * w<sub>i</sub> = exp(-d<sub>i</sub><sup>2</sup> / L<sup>2</sup>c ) + * </pre> + * + * where: + * <ul> + * <li><code>w<sub>i</sub></code> is the <b>weight</b> of the i'th observation point value + * <li><code>d<sub>i</sub></code> is the <b>distance</b> from the grid cell being estimated to the i'th observation point + * <li><code>L</code> is the <b>length scale</b>, which is determined by the observation spacing + * and the natural scale of the phenomena being measured. + * The length scale is in the units of the coordinate system of the data points. + * It will likely need to be empirically estimated. + * <li><code>c</code> is the <b>convergence factor</b>, which controls how much refinement takes place during each refinement step. + * In the first pass the convergence is automatically set to 1. + * For subsequent passes a value in the range 0.2 - 0.3 is usually effective. + * </ul> + * During refinement passes the value at each grid cell is re-estimated as: + * + * <pre> + * E<sub>g</sub>' = E<sub>g</sub> + sum( w<sub>i</sub> * (o<sub>i</sub> - E<sub>i</sub>) ) / sum( w<sub>i</sub> ) + * </pre> + * + * To optimize performance for large input datasets, it is only necessary to provide + * the data points which affect the surface interpolation within the + * specified output extent. In order to avoid "edge effects", the provided data points + * should be taken from an area somewhat larger than the output extent. + * The extent of the data area depends on the length scale, convergence factor, and data spacing in a complex way. + * A reasonable heuristic for determining the size of the query extent is to expand the output extent by a value of 2L. + * <p> + * Since the visual quality and accuracy of the computed surface is lower further from valid observations, + * the algorithm allows limiting the extent of the + * computed cells. This is done by using the concept of <b>supported grid cells</b>. + * Grid cells are supported by the + * input observations if they are within a specified distance of a specified number of observation points. + * Grid cells which are not supported are not + * computed and are output as NO_DATA values. + * <p> + * <b>References</b> + * <ol> + * <li>Barnes, S. L (1964). "A technique for maximizing details in numerical weather-map analysis". <i>Journal of Applied Meterology</i> 3 (4): 396 - 409 + * </ol> + * + * @author Martin Davis - OpenGeo + * + */ +public class BarnesSurfaceInterpolator { + + /** + * The default grid cell value used to indicate no data was computed for that cell + */ + public static final float DEFAULT_NO_DATA_VALUE = -999; + + private static final double INTERNAL_NO_DATA = Double.NaN; + + // =========== Input parameters + /** + * These parameters control which grid points are considered to be supported, i.e. have enough nearby observation points to be reasonably + * estimated. + * + * A grid point is supported if it has: + * + * count(obs within maxObservationDistance) >= minObservationCount + * + * Using these parameters is optional, but recommended, since estimating grid points which are far from any observations can produce unrealistic + * surfaces. + */ + private int minObservationCount = 2; + + private double maxObservationDistance = 0.0; + + private double convergenceFactor = 0.3; + + private double lengthScale = 0.0; + + private int passCount = 1; + + private Coordinate[] inputObs; + + // ============= Internal parameters (could be exposed) + private float noDataValue = DEFAULT_NO_DATA_VALUE; + + // ============= Computed parameters + // private double effectiveRadius; + + /** + * Indicates whether estimated grid points are filtered based on distance from observations + */ + private boolean useObservationMask; + + // ============ Working data + private float[] estimatedObs; + + /** + * Creates a Barnes Interpolator over a specified dataset of observation values. The observation data is provided as an array of + * {@link Coordinate} values, where the X,Y ordinates are the observation location, and the Z ordinate contains the observation value. + * + * @param data the observed data values + */ + public BarnesSurfaceInterpolator(Coordinate[] observationData) { + this.inputObs = observationData; + } + + /** + * Sets the number of passes performed during Barnes interpolation. + * + * @param passCount the number of estimation passes to perform (1 or more) + */ + public void setPassCount(int passCount) { + if (passCount < 1) + return; + this.passCount = passCount; + } + + /** + * Sets the length scale for the interpolation weighting function. The length scale is determined from the distance between the observation + * points, as well as the scale of the phenomena which is being measured. + * <p> + * + * + * @param lengthScale + */ + public void setLengthScale(double lengthScale) { + this.lengthScale = lengthScale; + } + + /** + * Sets the convergence factor used during refinement passes. The value should be in the range [0,1]. Empirically, values between 0.2 - 0.3 are + * most effective. Smaller values tend to make the interpolated surface too "jittery". Larger values produce less refinement effect. + * + * @param convergenceFactor the factor determining how much to refine the surface estimate + */ + public void setConvergenceFactor(double convergenceFactor) { + this.convergenceFactor = convergenceFactor; + } + + /** + * Sets the maximum distance from an observation for a grid point to be supported by that observation. Empirically determined; a reasonable + * starting point is between 1.5 and 2 times the Length scale. If the value is 0 (which is the default), all grid points are considered to be + * supported, and will thus be computed. + * + * @param maxObsDistance the maximum distance from an observation for a supported grid point + */ + public void setMaxObservationDistance(double maxObsDistance) { + this.maxObservationDistance = maxObsDistance; + } + + /** + * Sets the minimum number of in-range observations which are required for a grid point to be supported. The default is 2. + * + * @param minObsCount the minimum in-range observation count for supported grid points + */ + public void setMinObservationCount(int minObsCount) { + this.minObservationCount = minObsCount; + } + + /** + * Sets the NO_DATA value used to indicate that a grid cell was not computed. This value should be distinct from any potential data value. + * + * @param noDataValue the value to use to represent NO_DATA. + */ + public void setNoData(float noDataValue) { + this.noDataValue = noDataValue; + } + + /** + * Computes the estimated values for a regular grid of cells. The area covered by the grid is specified by an {@link Envelope}. The size of the + * grid is specified by the cell count for the grid width (X) and height (Y). + * + * @param srcEnv the area covered by the grid + * @param xSize the width of the grid + * @param ySize the height of the grid + * + * @return the computed grid of estimated data values (in row-major order) + */ + public float[][] computeSurface(Envelope srcEnv, int xSize, int ySize) { + // not currently used + // effectiveRadius = effectiveRadius(minimumWeight, influenceRadius); + + useObservationMask = minObservationCount > 0 && maxObservationDistance > 0.0; + + float[][] grid = new float[xSize][ySize]; + GridTransform trans = new GridTransform(srcEnv, xSize, ySize); + + estimateGrid(grid, trans); + + if (passCount > 1) { + /** + * First refinement pass requires observation points to be estimated as well + */ + estimatedObs = computeEstimatedObservations(); + refineGrid(grid, trans); + + /** + * For subsequent refinement passes, refine observations then recompute + */ + for (int i = 3; i <= passCount; i++) { + refineEstimatedObservations(estimatedObs); + refineGrid(grid, trans); + } + } + return grid; + } + + private float[] computeEstimatedObservations() { + float[] estimate = new float[inputObs.length]; + for (int i = 0; i < inputObs.length; i++) { + Coordinate dp = inputObs[i]; + float est = (float) estimatedValue(dp.x, dp.y); + if (! Float.isNaN(est)) + estimate[i] = est; + else + estimate[i] = (float) inputObs[i].z; + } + return estimate; + } + + private float[] refineEstimatedObservations(float[] currEst) { + float[] estimate = new float[inputObs.length]; + for (int i = 0; i < inputObs.length; i++) { + Coordinate dp = inputObs[i]; + float del = (float) refinedDelta(dp.x, dp.y, convergenceFactor); + if (! Float.isNaN(del)) + estimate[i] = (float) currEst[i] + del; + else + estimate[i] = (float) inputObs[i].z; + } + return estimate; + } + + /** + * Computes an initial estimate of the interpolated surface. + * + * @param grid the grid matrix buffer to use + * @param trans the transform mapping from data space to the grid + */ + private void estimateGrid(float[][] grid, GridTransform trans) { + for (int i = 0; i < grid.length; i++) { + for (int j = 0; j < grid[0].length; j++) { + double x = trans.x(i); + double y = trans.y(j); + + grid[i][j] = (float) noDataValue; + if (useObservationMask && !isSupportedGridPt(x, y)) + continue; + + float est = (float) estimatedValue(x, y); + if (!Float.isNaN(est)) + grid[i][j] = est; + } + } + } + + /** + * Computes a refined estimate for the interpolated surface. + * + * @param grid the grid matrix buffer to use + * @param trans the transform mapping from data space to the grid + */ + private void refineGrid(float[][] grid, GridTransform trans) { + for (int i = 0; i < grid.length; i++) { + for (int j = 0; j < grid[0].length; j++) { + double x = trans.x(i); + double y = trans.y(j); + + // skip NO_DATA values + if (grid[i][j] == noDataValue) + continue; + + float del = (float) refinedDelta(x, y, convergenceFactor); + /* + // DEBUGGING + if (del < 0) { + float d = (float) refinedDelta(x, y, convergenceFactor); + } + */ + if (! Float.isNaN(del)) + grid[i][j] = grid[i][j] + del; + } + } + } + + private boolean isSupportedGridPt(double x, double y) { + int count = 0; + for (int i = 0; i < inputObs.length; i++) { + double dist = distance(x, y, inputObs[i]); + if (dist <= maxObservationDistance) + count++; + } + return count >= minObservationCount; + } + + private double distance(double x, double y, Coordinate p) { + double dx = x - p.x; + double dy = y - p.y; + return Math.sqrt(dx * dx + dy * dy); + } + + /** + * Computes the initial estimate for a grid point. + * + * @param x the x ordinate of the grid point location + * @param y the y ordinate of the grid point location + * @return the estimated value, or INTERNAL_NO_DATA if the grid cell is not supported + */ + private double estimatedValue(double x, double y) { + Coordinate p = new Coordinate(x, y); + + double sumWgtVal = 0; + double sumWgt = 0; + int dataCount = 0; + for (int i = 0; i < inputObs.length; i++) { + double wgt = weight(p, inputObs[i], lengthScale); + /** + * Skip observation if unusable due to too great a distance + */ + if (Double.isNaN(wgt)) + continue; + + sumWgtVal += wgt * inputObs[i].z; + sumWgt += wgt; + dataCount++; + } + /** + * If grid point is not supported, return NO_DATA + */ + if (dataCount < minObservationCount) + return INTERNAL_NO_DATA; + return sumWgtVal / sumWgt; + } + + /** + * Computes a refinement delta, which is added to a grid point estimated value + * to refine the estimate. + * + * @param x the x ordinate of the grid point location + * @param y the y ordinate of the grid point location + * @param convergenceFactor the convergence factor + * @return the refinement delta value, or INTERNAL_NO_DATA if the grid cell is not supported + */ + private double refinedDelta(double x, double y, double convergenceFactor) { + Coordinate p = new Coordinate(x, y); + + double sumWgtVal = 0; + double sumWgt = 0; + int dataCount = 0; + for (int i = 0; i < inputObs.length; i++) { + double wgt = weight(p, inputObs[i], lengthScale, convergenceFactor); + /** + * Check if observation is unusable (e.g. due to too great a distance) + */ + if (Double.isNaN(wgt)) + continue; + + sumWgtVal += wgt * (inputObs[i].z - estimatedObs[i]); + sumWgt += wgt; + dataCount++; + } + /** + * If grid point is not supported, return NO_DATA + */ + if (dataCount < minObservationCount) + return INTERNAL_NO_DATA; + return sumWgtVal / sumWgt; + } + + private double weight(Coordinate dataPt, Coordinate gridPt, double lengthScale) { + return weight(gridPt, dataPt, lengthScale, 1.0); + } + + private double weight(Coordinate dataPt, Coordinate gridPt, double lengthScale, + double convergenceFactor) { + double dist = dataPt.distance(gridPt); + return weight(dist, lengthScale, convergenceFactor); + } + + private double weight(double dist, double lengthScale, double convergenceFactor) { + /** + * MD - using an effective radius is problematic. + * + * The effective radius grows as a log function of the cutoff weight, so even for very small cutoff weight values, the effective radius is + * only a few times the size of the influence radius. + * + * Also, dropping observation terms from the estimate results in very drastic (discontinuous) changes at distances around the effective + * radius. (Probably because beyond that distance there are very few terms (maybe only 2) contributing to the estimate, so there is no + * smoothing effect from incorporating many estimates) + * + * So - don't use effectiveRadius. + * + * Or, maybe it's ok as long as a observation mask is used as well, since the effect only occurs at large distances from observation points? + */ + /* + * if (dist > effectiveRadius) return INTERNAL_NO_DATA; // + */ + double dr = dist / lengthScale; + double w = Math.exp(-(dr * dr / convergenceFactor)); + // if (dist > cutoffRadius) System.out.println(w); + return w; + } + + /** + * Computes effective radius which is determined by the specified cutoff weight and the radius of the decay function. + * + * @param cutoffWeight + * @param radius + * @return + */ + private double effectiveRadius(double cutoffWeight, double radius) { + double cutoffFactor = Math.sqrt(-Math.log(cutoffWeight)); + double effRadius = radius * cutoffFactor; + double w = weight(effRadius, radius, 1.0); + System.out.println(cutoffWeight + " " + w); + return effRadius; + } + +} Added: trunk/modules/unsupported/process-raster/src/main/java/org/geotools/process/raster/surface/BarnesSurfaceProcess.java =================================================================== --- trunk/modules/unsupported/process-raster/src/main/java/org/geotools/process/raster/surface/BarnesSurfaceProcess.java (rev 0) +++ trunk/modules/unsupported/process-raster/src/main/java/org/geotools/process/raster/surface/BarnesSurfaceProcess.java 2012-06-18 18:28:10 UTC (rev 38813) @@ -0,0 +1,439 @@ +/* + * GeoTools - The Open Source Java GIS Toolkit + * http://geotools.org + * + * (C) 2011, Open Source Geospatial Foundation (OSGeo) + * (C) 2008-2011 TOPP - www.openplans.org. + * + * 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.process.raster.surface; + +import java.util.ArrayList; +import java.util.List; + +import javax.measure.unit.NonSI; +import javax.measure.unit.SI; +import javax.measure.unit.Unit; + +import org.geotools.coverage.CoverageFactoryFinder; +import org.geotools.coverage.grid.GridCoverage2D; +import org.geotools.coverage.grid.GridCoverageFactory; +import org.geotools.data.Query; +import org.geotools.data.simple.SimpleFeatureCollection; +import org.geotools.data.simple.SimpleFeatureIterator; +import org.geotools.factory.Hints; +import org.geotools.filter.text.cql2.CQLException; +import org.geotools.filter.text.ecql.ECQL; +import org.geotools.geometry.jts.ReferencedEnvelope; +import org.geotools.process.ProcessException; +import org.geotools.process.factory.DescribeParameter; +import org.geotools.process.factory.DescribeProcess; +import org.geotools.process.factory.DescribeResult; +import org.geotools.process.gs.GSProcess; +import org.geotools.referencing.CRS; +import org.opengis.coverage.grid.GridCoverage; +import org.opengis.coverage.grid.GridGeometry; +import org.opengis.feature.simple.SimpleFeature; +import org.opengis.filter.Filter; +import org.opengis.filter.expression.Expression; +import org.opengis.referencing.FactoryException; +import org.opengis.referencing.crs.CoordinateReferenceSystem; +import org.opengis.referencing.operation.MathTransform; +import org.opengis.util.ProgressListener; + +import com.vividsolutions.jts.geom.Coordinate; +import com.vividsolutions.jts.geom.CoordinateArrays; +import com.vividsolutions.jts.geom.Envelope; +import com.vividsolutions.jts.geom.Geometry; + +/** + * A Process that uses a {@link BarnesSurfaceInterpolator} to compute an interpolated surface + * over a set of irregular data points as a {@link GridCoverage}. + * <p> + * The implementation allows limiting the radius of influence of observations, in order to + * prevent extrapolation into unsupported areas, and to increase performance (by reducing + * the number of observations considered). + * <p> + * To improve performance, the surface grid can be computed at a lower resolution than the requested output image. + * The grid is upsampled to match the required image size. + * Upsampling uses Bilinear Interpolation to maintain visual quality. + * This gives a large improvement in performance, with minimal impact + * on visual quality for small cell sizes (for instance, 10 pixels or less). + * + * To ensure that the computed surface is stable + * (i.e. does not display obvious edge artifacts during zooming, panning and tiling), + * the data query extent should be expanded to be larger than the specified output extent. + * This includes "nearby" points which may affect the value of the surface. + * The expansion distance depends on the + * length scale, convergence factor, and data spacing + * in a complex way, so must be manually determined. + * It does NOT depend on the output window extent. + * (A good heuristic is to set it to expand by at least the size of the length scale.) + * + * To prevent excessive CPU consumption, the process allows limiting the number of data points + * to process. If the limit is exceeded the output is computed consuming and using only the + * maximum number of points specified. + * + * <h3>Parameters</h3> + * <i>M = mandatory, O = optional</i> + * <p> + * <ul> + * <li><b>data</b> (M) - the FeatureCollection containing the point observations + * <li><b>valueAttr</b> (M)- the feature type attribute containing the observed surface value + * <li><b>dataLimit</b> (O)- the maximum number of input points to process + * <li><b>scale</b> (M) - the Length Scale for the interpolation. In units of the input data CRS. + * <li><b>convergence</b> (O) - the convergence factor for refinement. Between 0 and 1 (values below 0.4 are safest). (Default = 0.3) + * <li><b>passes</b> (O) - the number of passes to compute. 1 or greater. (Default = 2) + * <li><b>minObservations</b> (O) - The minimum number of observations required to support a grid cell. (Default = 2) + * <li><b>maxObservationDistance</b> (O) - The maximum distance to an observation for it to support a grid cell. 0 means all observations are used. In units of the input data CRS. (Default = 0) + * <li><b>noDataValue</b> (O) - The NO_DATA value to use for unsupported grid cells in the output coverage. (Default = -999) + * <li><b>pixelsPerCell</b> (O) - The pixels-per-cell value determines the resolution of the computed grid. + * Larger values improve performance, but may degrade appearance. (Default = 1) + * <li><b>queryBuffer</b> (O) - The distance to expand the query envelope by. Larger values provide a more stable surface. In units of the input data CRS. (Default = 0) + * <li><b>outputBBOX</b> (M) - The georeferenced bounding box of the output area + * <li><b>outputWidth</b> (M) - The width of the output raster + * <li><b>outputHeight</b> (M) - The height of the output raster + * </ul> + * The output of the process is a {@linkplain GridCoverage2D} with a single band, + * with cell values in the same domain as the input observation field specified by <code>valueAttr</code>. + * <p> + * Computation of the surface takes places in the CRS of the output. + * If the data CRS is geodetic and the output CRS is planar, or vice-versa, + * the input points are transformed into the output CRS. + * A simple technique is used to convert the surface distance parameters + * <code>scale</code> and <code>maxObservationDistance</code> into the output CRS units. + * + * <h3>Using the process as a Rendering Transformation</h3> + * + * This process can be used as a RenderingTransformation, since it + * implements the <tt>invertQuery(... Query, GridGeometry)</tt> method. + * <p> + * When used as an Rendering Transformation the process rewrites data query to expand the query BBOX. + * This includes "nearby" data points to make the + * computed surface stable under panning and zooming. + * To support this the <code>queryBuffer</code> parameter should be specified to expand + * the query extent appropriately. + * <p> + * The output raster parameters can be determined from the request extents, using the + * following SLD environment variables: + * <p> + * <ul> + * <li><b>outputBBOX</b> - env var = <tt>wms_bbox</tt> + * <li><b>outputWidth</b> - env var = <tt>wms_width</tt> + * <li><b>outputHeight</b> - env var = <tt>wms_height</tt> + * </ul> + * + * <p> + * @author Martin Davis - OpenGeo + * + */ + +@DescribeProcess(title = "BarnesSurface", description = "Uses Barnes Analysis to compute an interpolated surface over a set of irregular data points aa a GridCoverage.") +public class BarnesSurfaceProcess implements GSProcess { + + // no process state is defined, since RenderingTransformation processes must be stateless + + @DescribeResult(name = "result", description = "The interpolated surface as a raster") + public GridCoverage2D execute( + + // process data + @DescribeParameter(name = "data", description = "Features containing the point observations to be interpolated") SimpleFeatureCollection obsFeatures, + @DescribeParameter(name = "valueAttr", description = "Featuretype attribute containing the observed surface value") String valueAttr, + @DescribeParameter(name = "dataLimit", description = "Limits the number of input features processed", min=0, max=1) Integer argDataLimit, + + // process parameters + @DescribeParameter(name = "scale", description = "Length scale to use for the interpolation", min=1, max=1) Double argScale, + @DescribeParameter(name = "convergence", description = "Convergence factor for the interpolation (default: 0.3)", min=0, max=1) Double argConvergence, + @DescribeParameter(name = "passes", description = "Number of passes to compute (default: 2)", min=0, max=1) Integer argPasses, + @DescribeParameter(name = "minObservations", description = "Minimum number of observations required to support a grid cell (default: 2)", min=0, max=1) Integer argMinObsCount, + @DescribeParameter(name = "maxObservationDistance", description = "Maximum distance to a supporting observation (default: 0)", min=0, max=1) Double argMaxObsDistance, + @DescribeParameter(name = "noDataValue", description = "Value to use for NO_DATA cells (default: -999)", min=0, max=1) Double argNoDataValue, + @DescribeParameter(name = "pixelsPerCell", description = "Number of pixels per grid cell (default = 1)", min=0, max=1) Integer argPixelsPerCell, + + // query modification parameters + @DescribeParameter(name = "queryBuffer", description = "Distance by which to expand the query window (default: 0)", min=0, max=1) Double argQueryBuffer, + + // output image parameters + @DescribeParameter(name = "outputBBOX", description = "Georeferenced bounding box of the output") ReferencedEnvelope outputEnv, + @DescribeParameter(name = "outputWidth", description = "Width of the output raster") Integer outputWidth, + @DescribeParameter(name = "outputHeight", description = "Height of the output raster") Integer outputHeight, + + ProgressListener monitor) throws ProcessException { + + /**--------------------------------------------- + * Check that process arguments are valid + * --------------------------------------------- + */ + if (valueAttr == null || valueAttr.length() <= 0) { + throw new IllegalArgumentException("Value attribute must be specified"); + } + + /**--------------------------------------------- + * Set up required information from process arguments. + * --------------------------------------------- + */ + int dataLimit = 0; + if (argDataLimit != null) dataLimit = argDataLimit; + + double lengthScale = argScale; + double convergenceFactor = argConvergence != null ? argConvergence : 0.3; + int passes = argPasses != null ? argPasses : 2; + int minObsCount = argMinObsCount != null ? argMinObsCount : 2; + double maxObsDistance = argMaxObsDistance != null ? argMaxObsDistance : 0.0; + float noDataValue = (float) (argNoDataValue != null ? argNoDataValue : -999); + int pixelsPerCell = 1; + if (argPixelsPerCell != null && argPixelsPerCell > 1) { + pixelsPerCell = argPixelsPerCell; + } + int gridWidth = outputWidth; + int gridHeight = outputHeight; + if (pixelsPerCell > 1) { + gridWidth = outputWidth / pixelsPerCell; + gridHeight = outputHeight / pixelsPerCell; + } + + CoordinateReferenceSystem srcCRS = obsFeatures.getSchema().getCoordinateReferenceSystem(); + CoordinateReferenceSystem dstCRS = outputEnv.getCoordinateReferenceSystem(); + MathTransform trans = null; + try { + trans = CRS.findMathTransform(srcCRS, dstCRS); + } catch (FactoryException e) { + throw new ProcessException(e); + } + /**--------------------------------------------- + * Convert distance parameters to units of the destination CRS. + * --------------------------------------------- + */ + double distanceConversionFactor = distanceConversionFactor(srcCRS, dstCRS); + double dstLengthScale = lengthScale * distanceConversionFactor; + double dstMaxObsDistance = maxObsDistance * distanceConversionFactor; + + /**--------------------------------------------- + * Extract the input observation points + * --------------------------------------------- + */ + Coordinate[] pts = null; + try { + pts = extractPoints(obsFeatures, valueAttr, trans, dataLimit); + } catch (CQLException e) { + throw new ProcessException(e); + } + + /**--------------------------------------------- + * Do the processing + * --------------------------------------------- + */ + //Stopwatch sw = new Stopwatch(); + // interpolate the surface at the specified resolution + float[][] barnesGrid = createBarnesGrid(pts, dstLengthScale, convergenceFactor, passes, minObsCount, dstMaxObsDistance, noDataValue, outputEnv, gridWidth, gridHeight); + + // flip now, since grid size may be smaller + barnesGrid = flipXY(barnesGrid); + + // upsample to output resolution if necessary + float[][] outGrid = barnesGrid; + if (pixelsPerCell > 1) + outGrid = upsample(barnesGrid, noDataValue, outputWidth, outputHeight); + + // convert to the GridCoverage2D required for output + GridCoverageFactory gcf = CoverageFactoryFinder.getGridCoverageFactory(null); + GridCoverage2D gridCov = gcf.create("Process Results", outGrid, outputEnv); + + //System.out.println("************** Barnes Surface computed in " + sw.getTimeString()); + + return gridCov; + } + + /* + * An approximate value for the length of a degree at the equator in meters. + * This doesn't have to be precise, since it is only used to convert + * values which are themselves rough approximations. + */ + private static final double METRES_PER_DEGREE = 111320; + + private static double distanceConversionFactor(CoordinateReferenceSystem srcCRS,CoordinateReferenceSystem dstCRS) + { + Unit<?> srcUnit = srcCRS.getCoordinateSystem().getAxis(0).getUnit(); + Unit<?> dstUnit = dstCRS.getCoordinateSystem().getAxis(0).getUnit(); + if (srcUnit == dstUnit) { + return 1; + } + else if (srcUnit == NonSI.DEGREE_ANGLE && dstUnit == SI.METER) { + return METRES_PER_DEGREE; + } + else if (srcUnit == SI.METER && dstUnit == NonSI.DEGREE_ANGLE) { + return 1.0 / METRES_PER_DEGREE; + } + throw new IllegalStateException("Unable to convert distances from " + srcUnit + " to " + dstUnit); + } + + /** + * Flips an XY matrix along the X=Y axis, and inverts the Y axis. + * Used to convert from "map orientation" into the "image orientation" + * used by GridCoverageFactory. + * The surface interpolation is done on an XY grid, with Y=0 being the bottom of the space. + * GridCoverages are stored in an image format, in a YX grid with 0 being the top. + * + * @param grid the grid to flip + * @return the flipped grid + */ + private static float[][] flipXY(float[][] grid) + { + int xsize = grid.length; + int ysize = grid[0].length; + + float[][] grid2 = new float[ysize][xsize]; + for (int ix = 0; ix < xsize; ix++) { + for (int iy = 0; iy < ysize; iy++) { + int iy2 = ysize - iy - 1; + grid2[iy2][ix] = grid[ix][iy]; + } + } + return grid2; + } + + private float[][] createBarnesGrid(Coordinate[] pts, + double lengthScale, + double convergenceFactor, + int passes, + int minObservationCount, + double maxObservationDistance, + float noDataValue, + Envelope destEnv, + int width, int height) + { + BarnesSurfaceInterpolator barnesInterp = new BarnesSurfaceInterpolator(pts); + barnesInterp.setLengthScale(lengthScale); + barnesInterp.setConvergenceFactor(convergenceFactor); + barnesInterp.setPassCount(passes); + barnesInterp.setMinObservationCount(minObservationCount); + barnesInterp.setMaxObservationDistance(maxObservationDistance); + barnesInterp.setNoData(noDataValue); + + float[][] grid = barnesInterp.computeSurface(destEnv, width, height); + + return grid; + } + + private float[][] upsample(float[][] grid, + float noDataValue, + int width, + int height) { + BilinearInterpolator bi = new BilinearInterpolator(grid, noDataValue); + float[][] outGrid = bi.interpolate(width, height, true); + return outGrid; + } + + /** + * Given a target query and a target grid geometry + * returns the query to be used to read the input data of the process involved in rendering. In + * this process this method is used to: + * <ul> + * <li>determine the extent & CRS of the output grid + * <li>expand the query envelope to ensure stable surface generation + * <li>modify the query hints to ensure point features are returned + * </ul> + * Note that in order to pass validation, all parameters named here must also appear + * in the parameter list of the <tt>execute</tt> method, + * even if they are not used there. + * + * @param argQueryBuffer the distance by which to expand the query window + * @param targetQuery the query used against the data source + * @param targetGridGeometry the grid geometry of the destination image + * @return The transformed query + */ + public Query invertQuery( + @DescribeParameter(name = "queryBuffer", description = "The distance by which to expand the query window", min=0, max=1) Double argQueryBuffer, + Query targetQuery, GridGeometry targetGridGeometry) + throws ProcessException { + + // default is no expansion + double queryBuffer = 0; + if (argQueryBuffer != null) { + queryBuffer = argQueryBuffer; + } + + targetQuery.setFilter(expandBBox(targetQuery.getFilter(), queryBuffer)); + + // clear properties to force all attributes to be read + // (required because the SLD processor cannot see the value attribute specified in the transformation) + // TODO: set the properties to read only the specified value attribute + targetQuery.setProperties(null); + + // set the decimation hint to ensure points are read + Hints hints = targetQuery.getHints(); + hints.put(Hints.GEOMETRY_DISTANCE, 0.0); + + return targetQuery; + } + + private Filter expandBBox(Filter filter, double distance) { + return (Filter) filter.accept( + new BBOXExpandingFilterVisitor(distance, distance, distance, distance), null); + } + + public static Coordinate[] extractPoints(SimpleFeatureCollection obsPoints, String attrName, MathTransform trans, int dataLimit) throws CQLException + { + Expression attrExpr = ECQL.toExpression(attrName); + List<Coordinate> ptList = new ArrayList<Coordinate>(); + SimpleFeatureIterator obsIt = obsPoints.features(); + + double[] srcPt = new double[2]; + double[] dstPt = new double[2]; + + int i = 0; + try { + while (obsIt.hasNext()) { + SimpleFeature feature = obsIt.next(); + + double val = 0; + + try { + + if (dataLimit > 0 && i >= dataLimit) { + //TODO: log this situation + break; + } + i++; + // get the observation value from the attribute (if non-null) + Object valObj = attrExpr.evaluate(feature); + if (valObj != null) { + // System.out.println(evaluate); + Number valNum = (Number) valObj; + val = valNum.doubleValue(); + + // get the point location from the geometry + Geometry geom = (Geometry) feature.getDefaultGeometry(); + Coordinate p = geom.getCoordinate(); + srcPt[0] = p.x; + srcPt[1] = p.y; + trans.transform(srcPt, 0, dstPt, 0, 1); + Coordinate pobs = new Coordinate(dstPt[0], dstPt[1], val); + //Coordinate pobs = new Coordinate(p.x, p.y, val); + ptList.add(pobs); + } + } + catch (Exception e) { + // just carry on for now (debugging) + //throw new ProcessException("Expression " + attrExpr + " failed to evaluate to a numeric value", e); + } + } + } + finally { + obsIt.close(); + } + + Coordinate[] pts = CoordinateArrays.toCoordinateArray(ptList); + return pts; + } + +} Added: trunk/modules/unsupported/process-raster/src/main/java/org/geotools/process/raster/surface/BilinearInterpolator.java =================================================================== --- trunk/modules/unsupported/process-raster/src/main/java/org/geotools/process/raster/surface/BilinearInterpolator.java (rev 0) +++ trunk/modules/unsupported/process-raster/src/main/java/org/geotools/process/raster/surface/BilinearInterpolator.java 2012-06-18 18:28:10 UTC (rev 38813) @@ -0,0 +1,203 @@ +/* + * GeoTools - The Open Source Java GIS Toolkit + * http://geotools.org + * + * (C) 2011, Open Source Geospatial Foundation (OSGeo) + * (C) 2008-2011 TOPP - www.openplans.org. + * + * 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.process.raster.surface; + +/** + * Interpolates a grid to a grid of different dimensions using bilinear interpolation. + * <p> + * NO_DATA cell values are supported in the source grid. + * There are two ways of handling the boundary between NO_DATA cells and data cells: + * <ul> + * <li><b>Truncate</b> - If any source cell is NO_DATA, the dest value is NO_DATA. + * This is simple and fast, but does make the data boundaries look a bit ragged. + * <li><b>Smooth</b> - If only one source cell is NO_DATA, the value is interpolated + * using the 3 valid values, across one-half of the interpolated cells. + * This smoothes off the boundary. + * If 2 or more source cells are NO_DATA, then Truncation is used. + * </ul> + * <p> + * Reference: http://en.wikipedia.org/wiki/Bilinear_interpolation. + * + * @author Martin Davis - OpenGeo + * + */ +public class BilinearInterpolator { + + private static final float NULL_NO_DATA = Float.NaN; + private final float[][] src; + private float noDataValue = NULL_NO_DATA; + + /** + * Creates a new interpolator on a given source grid. + * + * @param src the source grid + */ + public BilinearInterpolator(final float[][] src) + { + this(src, NULL_NO_DATA); + } + + /** + * Creates a new interpolator on a given source grid. + * + * @param src the source grid + * @param noDataValue the NO_DATA value (if none use Float.NaN) + */ + public BilinearInterpolator(final float[][] src, final float noDataValue) + { + this.src = src; + this.noDataValue = noDataValue; + } + + /** + * Interpolates the source grid to a new grid of different dimensions. + * + * @param width the width of the destination grid + * @param height the height of the destination grid + * @param smoothBoundary true if boundary smoothing should be performed + * @return the interpolated grid + */ + public float[][] interpolate(final int width, final int height, boolean smoothBoundary) + { + int srcWidth = src.length; + int srcHeight = src[0].length; + + float[][] dest = new float[width][height]; + + float xRatio = ((float) srcWidth - 1) / width ; + float yRatio = ((float) srcHeight - 1) / height ; + + for (int i = 0; i < width; i++) { + for (int j = 0; j < height; j++) { + float x = i * xRatio; + float y = j * yRatio; + int ix = (int) x; + int iy = (int) y; + float xfrac = x - ix; + float yfrac = y - iy; + + float val; + + if (ix < srcWidth - 1 && iy < srcHeight - 1) { + // interpolate if src cell is in grid + float v00 = src[ix][iy]; + float v10 = src[ix + 1][iy]; + float v01 = src[ix][iy + 1]; + float v11 = src[ix + 1][iy + 1]; + if (v00 == noDataValue + || v10 == noDataValue + || v01 == noDataValue + || v11 == noDataValue) { + // handle src cell with NO_DATA value(s) + if (smoothBoundary) { + val = interpolateBoundaryCell(xfrac, yfrac, v00, v10, v01, v11); + } + else { + val = noDataValue; + } + } + else { + // All src cell corners have values + // Compute bilinear interpolation over the src cell + val = ( v00*(1-xfrac)*(1-yfrac) + v10*(xfrac)*(1-yfrac) + + v01*(yfrac)*(1-xfrac) + v11*(xfrac*yfrac) + ) ; + } + } + else { + // dest index at edge of grid + val = src[ix][iy]; + } + dest[i][j] = val; + } + } + return dest; + } + + /** + * Interpolates across an edge grid cell, which has 1 or more NO_DATA values. + * Grid cells with 2 or or NO_DATA values still receive the value NO_DATA. + * Otherwise, the cell is interpolated across the triangle defined by the + * 3 valid corner values. + * This produces a much smoother edge appearance, containing 45-degree lines + * instead of a jagged stairstep boundary. + * + * @param xfrac the fractional x location of the interpolation point within the cell + * @param yfrac the fractional y location of the interpolation point within the cell + * @param v00 the lower left value + * @param v10 the lower right value + * @param v01 the upper left value + * @param v11 the upper right value + * @return the interpolated value + */ + private float interpolateBoundaryCell(float xfrac, float yfrac, float v00, float v10, float v01, float v11) + { + // count noData values + int count = 0; + if (v00 == noDataValue) count++; + if (v10 == noDataValue) count++; + if (v01 == noDataValue) count++; + if (v11 == noDataValue) count++; + + /** + * Cells with >= 2 noData ==> noData + */ + if (count > 1) return noDataValue; + + /** + * Now only one cell has noData value. + * Compute interpolation over cell, with vertex layout normalized to put noData in NE. + * This is done by flipping the cell across the X or Y axis, or both + * (and transforming the point offsets likewise) + */ + if (v00 == noDataValue) return interpolateBoundaryCellNorm(1.0f - yfrac, 1.0f - xfrac, v11, v10, v01); + if (v11 == noDataValue) return interpolateBoundaryCellNorm(xfrac, yfrac, v00, v10, v01); + if (v10 == noDataValue) return interpolateBoundaryCellNorm(xfrac, 1.0f - yfrac, v01, v11, v00); + if (v01 == noDataValue) return interpolateBoundaryCellNorm(1.0f - xfrac, yfrac, v10, v00, v11); + + // should never reach here + return noDataValue; + } + + /** + * Computes an interpolated value across a grid cell which has a single + * NO_DATA value in the NE corner (<tt>v11</tt>), and valid data values + * in the three other corners. + * The interpolated value is computed using linear interpolation across + * the 3D triangle defined by the three valid corners. + * + * @param xfrac the fractional x location of the interpolation point within the cell + * @param yfrac the fractional y location of the interpolation point within the cell + * @param v00 the lower left value + * @param v10 the lower right value + * @param v01 the upper left value + * @return the interpolated value + */ + private float interpolateBoundaryCellNorm(float xfrac, float yfrac, float v00, float v10, float v01) + { + // if point is in NE triangle, value is NO_DATA + if (xfrac + yfrac > 1) return noDataValue; + + // interpolate across plane defined by SW triangle and values + float dx = v10 - v00; + float dy = v01 - v00; + float v = v00 + (xfrac * dx) + (yfrac * dy); + return v; + } + +} Added: trunk/modules/unsupported/process-raster/src/main/java/org/geotools/process/raster/surface/GridTransform.java =================================================================== --- trunk/modules/unsupported/process-raster/src/main/java/org/geotools/process/raster/surface/GridTransform.java (rev 0) +++ trunk/modules/unsupported/process-raster/src/main/java/org/geotools/process/raster/surface/GridTransform.java 2012-06-18 18:28:10 UTC (rev 38813) @@ -0,0 +1,115 @@ +/* + * GeoTools - The Open Source Java GIS Toolkit + * http://geotools.org + * + * (C) 2011, Open Source Geospatial Foundation (OSGeo) + * (C) 2008-2011 TOPP - www.openplans.org. + * + * 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.process.raster.surface; + +import com.vividsolutions.jts.geom.Envelope; + +/** + * An affine transformation between two parallel + * coordinate systems, one defined by an {@link Envelope} + * and one defined by a discrete zero-based grid + * representing the same area as the envelope. + * The transformation incorporates an isotropic scaling and a translation. + * + * @author Martin Davis - OpenGeo + * + */ +class GridTransform { + + private Envelope env; + + private int xSize; + + private int ySize; + + private double dx; + + private double dy; + + /** + * Creates a new transform. + * + * @param env the envelope defining one coordinate system + * @param xSize the number of cells along the X axis of the grid + * @param ySize the number of cells along the Y axis of the grid + */ + public GridTransform(Envelope env, int xSize, int ySize) { + this.env = env; + this.xSize = xSize; + this.ySize = ySize; + dx = env.getWidth() / (xSize - 1); + dy = env.getHeight() / (ySize - 1); + } + + /** + * Computes the X ordinate of the i'th grid column. + * @param i the index of a grid column + * @return the X ordinate of the column + */ + public double x(int i) { + if (i >= xSize - 1) + return env.getMaxX(); + return env.getMinX() + i * dx; + } + + /** + * Computes the Y ordinate of the i'th grid row. + * @param j the index of a grid row + * @return the Y ordinate of the row + */ + public double y(int j) { + if (j >= ySize - 1) + return env.getMaxY(); + return env.getMinY() + j * dy; + } + + /** + * Computes the column index of an X ordinate. + * @param x the X ordinate + * @return the column index + */ + public int i(double x) { + if (x > env.getMaxX()) + return xSize; + if (x < env.getMinX()) + return -1; + int i = (int) ((x - env.getMinX()) / dx); + // have already check x is in bounds, so ensure returning a valid value + if (i >= xSize) + i = xSize - 1; + return i; + } + + /** + * Computes the column index of an Y ordinate. + * @param y the Y ordinate + * @return the column index + */ + public int j(double y) { + if (y > env.getMaxY()) + return ySize; + if (y < env.getMinY()) + return -1; + int j = (int) ((y - env.getMinY()) / dy); + // have already check x is in bounds, so ensure returning a valid value + if (j >= ySize) + j = ySize - 1; + return j; + } + +} \ No newline at end of file Added: trunk/modules/unsupported/process-raster/src/test/java/org/geotools/process/raster/surface/BarnesSurfaceProcessTest.java =================================================================== --- trunk/modules/unsupported/process-raster/src/test/java/org/geotools/process/raster/surface/BarnesSurfaceProcessTest.java (rev 0) +++ trunk/modules/unsupported/process-raster/src/test/java/org/geotools/process/raster/surface/BarnesSurfaceProcessTest.java 2012-06-18 18:28:10 UTC (rev 38813) @@ -0,0 +1,128 @@ +/* + * GeoTools - The Open Source Java GIS Toolkit + * http://geotools.org + * + * (C) 2002-2011, Open Source Geospatial Foundation (OSGeo) + * + * 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.process.raster.surface; + +import static org.junit.Assert.assertTrue; + +import java.awt.geom.Point2D; + +import org.geotools.coverage.grid.GridCoverage2D; +import org.geotools.data.simple.SimpleFeatureCollection; +import org.geotools.feature.FeatureCollections; +import org.geotools.feature.simple.SimpleFeatureBuilder; +import org.geotools.feature.simple.SimpleFeatureTypeBuilder; +import org.geotools.geometry.jts.ReferencedEnvelope; +import org.geotools.referencing.crs.DefaultGeographicCRS; +import org.junit.Test; +import org.opengis.feature.simple.SimpleFeatureType; +import org.opengis.util.ProgressListener; + +import com.vividsolutions.jts.geom.Coordinate; +import com.vividsolutions.jts.ge... [truncated message content] |
Author: aaime Date: 2012-06-16 10:09:07 -0700 (Sat, 16 Jun 2012) New Revision: 38812 Added: branches/2.7.x/modules/extension/wms/src/main/java/org/geotools/data/wms/xml/MetadataURL.java Modified: branches/2.7.x/modules/extension/wms/src/main/java/org/geotools/data/ows/Layer.java branches/2.7.x/modules/extension/wms/src/main/java/org/geotools/data/wms/xml/WMSComplexTypes.java branches/2.7.x/modules/extension/wms/src/test/java/org/geotools/data/wms/xml/test/WMSSchemaTest.java branches/2.7.x/modules/extension/wms/src/test/resources/org/geotools/data/wms/xml/test/test-data/1.3.0Capabilities.xml Log: [GEOT-4148] Getcapabilities MetadataURL added for layers, patch by Meine Toonen Modified: branches/2.7.x/modules/extension/wms/src/main/java/org/geotools/data/ows/Layer.java =================================================================== --- branches/2.7.x/modules/extension/wms/src/main/java/org/geotools/data/ows/Layer.java 2012-06-16 17:08:04 UTC (rev 38811) +++ branches/2.7.x/modules/extension/wms/src/main/java/org/geotools/data/ows/Layer.java 2012-06-16 17:09:07 UTC (rev 38812) @@ -30,6 +30,7 @@ import org.geotools.data.wms.xml.Dimension; import org.geotools.data.wms.xml.Extent; +import org.geotools.data.wms.xml.MetadataURL; import org.geotools.geometry.GeneralEnvelope; import org.geotools.geometry.jts.ReferencedEnvelope; import org.geotools.referencing.CRS; @@ -154,6 +155,8 @@ */ private Map<CoordinateReferenceSystem, Envelope> envelopeCache = Collections .synchronizedMap(new WeakHashMap<CoordinateReferenceSystem, Envelope>()); + + private List<MetadataURL> metadataURL; /** * Called to clear the internal cache of this layer; and any children. @@ -931,6 +934,15 @@ public void setCascaded(int cascadedValue) { this.cascaded = cascadedValue; } + + public List<MetadataURL> getMetadataURL() { + return metadataURL; + } + + public void setMetadataURL(List<MetadataURL> metadataURL) { + this.metadataURL = metadataURL; + } + @Override public String toString() { Added: branches/2.7.x/modules/extension/wms/src/main/java/org/geotools/data/wms/xml/MetadataURL.java =================================================================== --- branches/2.7.x/modules/extension/wms/src/main/java/org/geotools/data/wms/xml/MetadataURL.java (rev 0) +++ branches/2.7.x/modules/extension/wms/src/main/java/org/geotools/data/wms/xml/MetadataURL.java 2012-06-16 17:09:07 UTC (rev 38812) @@ -0,0 +1,72 @@ +/* + * GeoTools - The Open Source Java GIS Toolkit + * http://geotools.org + * + * (C) 2004-2008, Open Source Geospatial Foundation (OSGeo) + * + * 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.wms.xml; + +import java.net.URL; + +/** + * A Map Server may use zero or more MetadataURL elements to offer detailed, + * standardized metadata about the data underneath a particular layer. The type + * attribute indicates the standard to which the metadata complies. Two types + * are defined at present: 'TC211' = ISO TC211 19115; 'FGDC' = FGDC CSDGM. The + * format element indicates how the metadata is structured. --> + * <!ELEMENT MetadataURL (Format, OnlineResource) > + * <!ATTLIST MetadataURL + * type ( TC211 | FGDC ) #REQUIRED> + * @author Meine Toonen mei...@b3... + */ +public class MetadataURL { + + protected URL url; + protected String type; + protected String format; + + public MetadataURL(URL url, String type, String format) { + this.url = url; + this.type = type; + this.format = format; + } + + public String getFormat() { + return format; + } + + public void setFormat(String format) { + this.format = format; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public URL getUrl() { + return url; + } + + public void setUrl(URL url) { + this.url = url; + } + + @Override + public String toString() { + return "MetadataURL{" + "url=" + url + ", type=" + type + '}'; + } +} Modified: branches/2.7.x/modules/extension/wms/src/main/java/org/geotools/data/wms/xml/WMSComplexTypes.java =================================================================== --- branches/2.7.x/modules/extension/wms/src/main/java/org/geotools/data/wms/xml/WMSComplexTypes.java 2012-06-16 17:08:04 UTC (rev 38811) +++ branches/2.7.x/modules/extension/wms/src/main/java/org/geotools/data/wms/xml/WMSComplexTypes.java 2012-06-16 17:09:07 UTC (rev 38812) @@ -2599,6 +2599,7 @@ HashMap dimensions = new HashMap(); HashMap extents = new HashMap(); List styles = new ArrayList(); + List metadataURLS = new ArrayList(); for (int i = 0; i < value.length; i++) { if (sameName(elems[0], value[i])) { @@ -2657,9 +2658,10 @@ // if (sameName(elems[11], value[i])) { // //TODO identifier ignored // } - // if (sameName(elems[12], value[i])) { - // //TODO metadataURL ignore - // } + if (sameName(elems[12], value[i])) { + MetadataURL metadataUrl = (MetadataURL)value[i].getValue(); + metadataURLS.add(metadataUrl); + } // if (sameName(elems[13], value[i])) { // //TODO dataURL ignored // } @@ -2697,6 +2699,7 @@ layer.setDimensions(dimensions); layer.setExtents(extents); layer.setStyles(styles); + layer.setMetadataURL(metadataURLS); layer.setChildren((Layer[]) childLayers.toArray(new Layer[childLayers.size()])); @@ -3640,7 +3643,12 @@ public Object getValue(Element element, ElementValue[] value, Attributes attrs, Map hints) throws SAXException, OperationNotSupportedException { - return null; + String type = attrs.getValue("type").toString(); + URL url = (URL)value[1].getValue(); + String format = (String)(((Object[])value[0].getValue())[0]); + MetadataURL metadataURL = new MetadataURL( url, type, format); + + return metadataURL; // throw new OperationNotSupportedException(); } Modified: branches/2.7.x/modules/extension/wms/src/test/java/org/geotools/data/wms/xml/test/WMSSchemaTest.java =================================================================== --- branches/2.7.x/modules/extension/wms/src/test/java/org/geotools/data/wms/xml/test/WMSSchemaTest.java 2012-06-16 17:08:04 UTC (rev 38811) +++ branches/2.7.x/modules/extension/wms/src/test/java/org/geotools/data/wms/xml/test/WMSSchemaTest.java 2012-06-16 17:09:07 UTC (rev 38812) @@ -116,6 +116,9 @@ assertTrue(layer.isQueryable()); assertEquals(layer.getName(), "Bathymetry"); assertEquals(layer.getTitle(), "Bathymetry"); + assertEquals(layer.getMetadataURL().get(0).getUrl().toString(), "http://www.example.com/?"); + assertEquals(layer.getMetadataURL().get(0).getFormat(), "text/html"); + assertEquals(layer.getMetadataURL().get(0).getType(), "FGDC"); assertEquals(layer.getStyles().get(0).getLegendURLs().get(0), "http://www.osgeo.org/sites/all/themes/osgeo/logo.png"); // Added test to verify inheritance, should be same as previous llbbox Modified: branches/2.7.x/modules/extension/wms/src/test/resources/org/geotools/data/wms/xml/test/test-data/1.3.0Capabilities.xml =================================================================== --- branches/2.7.x/modules/extension/wms/src/test/resources/org/geotools/data/wms/xml/test/test-data/1.3.0Capabilities.xml 2012-06-16 17:08:04 UTC (rev 38811) +++ branches/2.7.x/modules/extension/wms/src/test/resources/org/geotools/data/wms/xml/test/test-data/1.3.0Capabilities.xml 2012-06-16 17:09:07 UTC (rev 38812) @@ -101,6 +101,10 @@ <Name>Bathymetry</Name> <Title>Bathymetry</Title> <BoundingBox CRS="CRS:84" minx="-180" miny="-90" maxx="180" maxy="90"/> + <MetadataURL type="FGDC"> + <Format>text/html</Format> + <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://www.example.com/?"/> + </MetadataURL> <Style> <Name>default</Name> <Title>default</Title> |
Author: aaime Date: 2012-06-16 10:08:04 -0700 (Sat, 16 Jun 2012) New Revision: 38811 Added: trunk/modules/extension/wms/src/main/java/org/geotools/data/wms/xml/MetadataURL.java Modified: trunk/modules/extension/wms/src/main/java/org/geotools/data/ows/Layer.java trunk/modules/extension/wms/src/main/java/org/geotools/data/wms/xml/WMSComplexTypes.java trunk/modules/extension/wms/src/test/java/org/geotools/data/wms/xml/test/WMSSchemaTest.java trunk/modules/extension/wms/src/test/resources/org/geotools/data/wms/xml/test/test-data/1.3.0Capabilities.xml Log: [GEOT-4148] Getcapabilities MetadataURL added for layers, patch by Meine Toonen Modified: trunk/modules/extension/wms/src/main/java/org/geotools/data/ows/Layer.java =================================================================== --- trunk/modules/extension/wms/src/main/java/org/geotools/data/ows/Layer.java 2012-06-16 16:45:48 UTC (rev 38810) +++ trunk/modules/extension/wms/src/main/java/org/geotools/data/ows/Layer.java 2012-06-16 17:08:04 UTC (rev 38811) @@ -30,6 +30,7 @@ import org.geotools.data.wms.xml.Dimension; import org.geotools.data.wms.xml.Extent; +import org.geotools.data.wms.xml.MetadataURL; import org.geotools.geometry.GeneralEnvelope; import org.geotools.geometry.jts.ReferencedEnvelope; import org.geotools.referencing.CRS; @@ -155,6 +156,8 @@ */ private Map<CoordinateReferenceSystem, Envelope> envelopeCache = Collections .synchronizedMap(new WeakHashMap<CoordinateReferenceSystem, Envelope>()); + + private List<MetadataURL> metadataURL; /** * Called to clear the internal cache of this layer; and any children. @@ -932,6 +935,15 @@ public void setCascaded(int cascadedValue) { this.cascaded = cascadedValue; } + + public List<MetadataURL> getMetadataURL() { + return metadataURL; + } + + public void setMetadataURL(List<MetadataURL> metadataURL) { + this.metadataURL = metadataURL; + } + @Override public String toString() { Added: trunk/modules/extension/wms/src/main/java/org/geotools/data/wms/xml/MetadataURL.java =================================================================== --- trunk/modules/extension/wms/src/main/java/org/geotools/data/wms/xml/MetadataURL.java (rev 0) +++ trunk/modules/extension/wms/src/main/java/org/geotools/data/wms/xml/MetadataURL.java 2012-06-16 17:08:04 UTC (rev 38811) @@ -0,0 +1,72 @@ +/* + * GeoTools - The Open Source Java GIS Toolkit + * http://geotools.org + * + * (C) 2004-2008, Open Source Geospatial Foundation (OSGeo) + * + * 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.wms.xml; + +import java.net.URL; + +/** + * A Map Server may use zero or more MetadataURL elements to offer detailed, + * standardized metadata about the data underneath a particular layer. The type + * attribute indicates the standard to which the metadata complies. Two types + * are defined at present: 'TC211' = ISO TC211 19115; 'FGDC' = FGDC CSDGM. The + * format element indicates how the metadata is structured. --> + * <!ELEMENT MetadataURL (Format, OnlineResource) > + * <!ATTLIST MetadataURL + * type ( TC211 | FGDC ) #REQUIRED> + * @author Meine Toonen mei...@b3... + */ +public class MetadataURL { + + protected URL url; + protected String type; + protected String format; + + public MetadataURL(URL url, String type, String format) { + this.url = url; + this.type = type; + this.format = format; + } + + public String getFormat() { + return format; + } + + public void setFormat(String format) { + this.format = format; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public URL getUrl() { + return url; + } + + public void setUrl(URL url) { + this.url = url; + } + + @Override + public String toString() { + return "MetadataURL{" + "url=" + url + ", type=" + type + '}'; + } +} Modified: trunk/modules/extension/wms/src/main/java/org/geotools/data/wms/xml/WMSComplexTypes.java =================================================================== --- trunk/modules/extension/wms/src/main/java/org/geotools/data/wms/xml/WMSComplexTypes.java 2012-06-16 16:45:48 UTC (rev 38810) +++ trunk/modules/extension/wms/src/main/java/org/geotools/data/wms/xml/WMSComplexTypes.java 2012-06-16 17:08:04 UTC (rev 38811) @@ -2598,6 +2598,7 @@ HashMap dimensions = new HashMap(); HashMap extents = new HashMap(); List styles = new ArrayList(); + List metadataURLS = new ArrayList(); for (int i = 0; i < value.length; i++) { if (sameName(elems[0], value[i])) { @@ -2656,9 +2657,10 @@ // if (sameName(elems[11], value[i])) { // //TODO identifier ignored // } - // if (sameName(elems[12], value[i])) { - // //TODO metadataURL ignore - // } + if (sameName(elems[12], value[i])) { + MetadataURL metadataUrl = (MetadataURL)value[i].getValue(); + metadataURLS.add(metadataUrl); + } // if (sameName(elems[13], value[i])) { // //TODO dataURL ignored // } @@ -2696,6 +2698,7 @@ layer.setDimensions(dimensions); layer.setExtents(extents); layer.setStyles(styles); + layer.setMetadataURL(metadataURLS); layer.setChildren((Layer[]) childLayers.toArray(new Layer[childLayers.size()])); @@ -3639,7 +3642,12 @@ public Object getValue(Element element, ElementValue[] value, Attributes attrs, Map hints) throws SAXException, OperationNotSupportedException { - return null; + String type = attrs.getValue("type").toString(); + URL url = (URL)value[1].getValue(); + String format = (String)(((Object[])value[0].getValue())[0]); + MetadataURL metadataURL = new MetadataURL( url, type, format); + + return metadataURL; // throw new OperationNotSupportedException(); } Modified: trunk/modules/extension/wms/src/test/java/org/geotools/data/wms/xml/test/WMSSchemaTest.java =================================================================== --- trunk/modules/extension/wms/src/test/java/org/geotools/data/wms/xml/test/WMSSchemaTest.java 2012-06-16 16:45:48 UTC (rev 38810) +++ trunk/modules/extension/wms/src/test/java/org/geotools/data/wms/xml/test/WMSSchemaTest.java 2012-06-16 17:08:04 UTC (rev 38811) @@ -121,6 +121,9 @@ assertTrue(layer.isQueryable()); assertEquals(layer.getName(), "Bathymetry"); assertEquals(layer.getTitle(), "Bathymetry"); + assertEquals(layer.getMetadataURL().get(0).getUrl().toString(), "http://www.example.com/?"); + assertEquals(layer.getMetadataURL().get(0).getFormat(), "text/html"); + assertEquals(layer.getMetadataURL().get(0).getType(), "FGDC"); assertEquals(layer.getStyles().get(0).getLegendURLs().get(0), "http://www.osgeo.org/sites/all/themes/osgeo/logo.png"); // Added test to verify inheritance, should be same as previous llbbox Modified: trunk/modules/extension/wms/src/test/resources/org/geotools/data/wms/xml/test/test-data/1.3.0Capabilities.xml =================================================================== --- trunk/modules/extension/wms/src/test/resources/org/geotools/data/wms/xml/test/test-data/1.3.0Capabilities.xml 2012-06-16 16:45:48 UTC (rev 38810) +++ trunk/modules/extension/wms/src/test/resources/org/geotools/data/wms/xml/test/test-data/1.3.0Capabilities.xml 2012-06-16 17:08:04 UTC (rev 38811) @@ -101,6 +101,10 @@ <Name>Bathymetry</Name> <Title>Bathymetry</Title> <BoundingBox CRS="CRS:84" minx="-180" miny="-90" maxx="180" maxy="90"/> + <MetadataURL type="FGDC"> + <Format>text/html</Format> + <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://www.example.com/?"/> + </MetadataURL> <Style> <Name>default</Name> <Title>default</Title> |
Author: aaime Date: 2012-06-16 09:45:48 -0700 (Sat, 16 Jun 2012) New Revision: 38810 Modified: branches/2.7.x/modules/extension/wms/src/main/java/org/geotools/data/wms/xml/WMSComplexTypes.java branches/2.7.x/modules/extension/wms/src/test/java/org/geotools/data/wms/xml/test/WMSSchemaTest.java branches/2.7.x/modules/extension/wms/src/test/resources/org/geotools/data/wms/xml/test/test-data/1.3.0Capabilities.xml Log: [GEOT-4149] Legendurls from wms getcapabilities, patch by Meine Toonen Modified: branches/2.7.x/modules/extension/wms/src/main/java/org/geotools/data/wms/xml/WMSComplexTypes.java =================================================================== --- branches/2.7.x/modules/extension/wms/src/main/java/org/geotools/data/wms/xml/WMSComplexTypes.java 2012-06-16 16:44:11 UTC (rev 38809) +++ branches/2.7.x/modules/extension/wms/src/main/java/org/geotools/data/wms/xml/WMSComplexTypes.java 2012-06-16 16:45:48 UTC (rev 38810) @@ -4143,6 +4143,7 @@ Attributes attrs, Map hints) throws SAXException, OperationNotSupportedException { StyleImpl style = new StyleImpl(); + List legendURLS = new ArrayList(); for (int i = 0; i < value.length; i++) { @@ -4162,7 +4163,7 @@ } if (sameName(elems[3], value[i])) { - //TODO Implement LegendURL + legendURLS.add((String)value[2].getValue()); } if (sameName(elems[4], value[i])) { @@ -4173,7 +4174,7 @@ //TODO implement StyleURL } } - + style.setLegendURLs(legendURLS); return style; } @@ -4274,7 +4275,9 @@ public Object getValue(Element element, ElementValue[] value, Attributes attrs, Map hints) throws SAXException, OperationNotSupportedException { - return null; + + String legendURL = value[1].getValue().toString(); + return legendURL; // throw new OperationNotSupportedException(); } Modified: branches/2.7.x/modules/extension/wms/src/test/java/org/geotools/data/wms/xml/test/WMSSchemaTest.java =================================================================== --- branches/2.7.x/modules/extension/wms/src/test/java/org/geotools/data/wms/xml/test/WMSSchemaTest.java 2012-06-16 16:44:11 UTC (rev 38809) +++ branches/2.7.x/modules/extension/wms/src/test/java/org/geotools/data/wms/xml/test/WMSSchemaTest.java 2012-06-16 16:45:48 UTC (rev 38810) @@ -116,6 +116,7 @@ assertTrue(layer.isQueryable()); assertEquals(layer.getName(), "Bathymetry"); assertEquals(layer.getTitle(), "Bathymetry"); + assertEquals(layer.getStyles().get(0).getLegendURLs().get(0), "http://www.osgeo.org/sites/all/themes/osgeo/logo.png"); // Added test to verify inheritance, should be same as previous llbbox llbbox = layer.getLatLonBoundingBox(); Modified: branches/2.7.x/modules/extension/wms/src/test/resources/org/geotools/data/wms/xml/test/test-data/1.3.0Capabilities.xml =================================================================== --- branches/2.7.x/modules/extension/wms/src/test/resources/org/geotools/data/wms/xml/test/test-data/1.3.0Capabilities.xml 2012-06-16 16:44:11 UTC (rev 38809) +++ branches/2.7.x/modules/extension/wms/src/test/resources/org/geotools/data/wms/xml/test/test-data/1.3.0Capabilities.xml 2012-06-16 16:45:48 UTC (rev 38810) @@ -100,7 +100,15 @@ <Layer queryable="1" opaque="1"> <Name>Bathymetry</Name> <Title>Bathymetry</Title> - <BoundingBox CRS="CRS:84" minx="-180" miny="-90" maxx="180" maxy="90"/> + <BoundingBox CRS="CRS:84" minx="-180" miny="-90" maxx="180" maxy="90"/> + <Style> + <Name>default</Name> + <Title>default</Title> + <LegendURL width="99" height="25"> + <Format>image/png</Format> + <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://www.osgeo.org/sites/all/themes/osgeo/logo.png"/> + </LegendURL> + </Style> </Layer> <Layer queryable="1" opaque="0"> <Name>Countries</Name> |
Author: aaime Date: 2012-06-16 09:44:11 -0700 (Sat, 16 Jun 2012) New Revision: 38809 Modified: trunk/modules/extension/wms/src/main/java/org/geotools/data/wms/xml/WMSComplexTypes.java trunk/modules/extension/wms/src/test/java/org/geotools/data/wms/xml/test/WMSSchemaTest.java trunk/modules/extension/wms/src/test/resources/org/geotools/data/wms/xml/test/test-data/1.3.0Capabilities.xml Log: [GEOT-4149] Legendurls from wms getcapabilities, patch by Meine Toonen Modified: trunk/modules/extension/wms/src/main/java/org/geotools/data/wms/xml/WMSComplexTypes.java =================================================================== --- trunk/modules/extension/wms/src/main/java/org/geotools/data/wms/xml/WMSComplexTypes.java 2012-06-16 10:34:20 UTC (rev 38808) +++ trunk/modules/extension/wms/src/main/java/org/geotools/data/wms/xml/WMSComplexTypes.java 2012-06-16 16:44:11 UTC (rev 38809) @@ -4142,6 +4142,7 @@ Attributes attrs, Map hints) throws SAXException, OperationNotSupportedException { StyleImpl style = new StyleImpl(); + List legendURLS = new ArrayList(); for (int i = 0; i < value.length; i++) { @@ -4161,7 +4162,7 @@ } if (sameName(elems[3], value[i])) { - //TODO Implement LegendURL + legendURLS.add((String)value[2].getValue()); } if (sameName(elems[4], value[i])) { @@ -4172,7 +4173,7 @@ //TODO implement StyleURL } } - + style.setLegendURLs(legendURLS); return style; } @@ -4273,7 +4274,9 @@ public Object getValue(Element element, ElementValue[] value, Attributes attrs, Map hints) throws SAXException, OperationNotSupportedException { - return null; + + String legendURL = value[1].getValue().toString(); + return legendURL; // throw new OperationNotSupportedException(); } Modified: trunk/modules/extension/wms/src/test/java/org/geotools/data/wms/xml/test/WMSSchemaTest.java =================================================================== --- trunk/modules/extension/wms/src/test/java/org/geotools/data/wms/xml/test/WMSSchemaTest.java 2012-06-16 10:34:20 UTC (rev 38808) +++ trunk/modules/extension/wms/src/test/java/org/geotools/data/wms/xml/test/WMSSchemaTest.java 2012-06-16 16:44:11 UTC (rev 38809) @@ -121,6 +121,7 @@ assertTrue(layer.isQueryable()); assertEquals(layer.getName(), "Bathymetry"); assertEquals(layer.getTitle(), "Bathymetry"); + assertEquals(layer.getStyles().get(0).getLegendURLs().get(0), "http://www.osgeo.org/sites/all/themes/osgeo/logo.png"); // Added test to verify inheritance, should be same as previous llbbox llbbox = layer.getLatLonBoundingBox(); Modified: trunk/modules/extension/wms/src/test/resources/org/geotools/data/wms/xml/test/test-data/1.3.0Capabilities.xml =================================================================== --- trunk/modules/extension/wms/src/test/resources/org/geotools/data/wms/xml/test/test-data/1.3.0Capabilities.xml 2012-06-16 10:34:20 UTC (rev 38808) +++ trunk/modules/extension/wms/src/test/resources/org/geotools/data/wms/xml/test/test-data/1.3.0Capabilities.xml 2012-06-16 16:44:11 UTC (rev 38809) @@ -100,7 +100,15 @@ <Layer queryable="1" opaque="1"> <Name>Bathymetry</Name> <Title>Bathymetry</Title> - <BoundingBox CRS="CRS:84" minx="-180" miny="-90" maxx="180" maxy="90"/> + <BoundingBox CRS="CRS:84" minx="-180" miny="-90" maxx="180" maxy="90"/> + <Style> + <Name>default</Name> + <Title>default</Title> + <LegendURL width="99" height="25"> + <Format>image/png</Format> + <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://www.osgeo.org/sites/all/themes/osgeo/logo.png"/> + </LegendURL> + </Style> </Layer> <Layer queryable="1" opaque="0"> <Name>Countries</Name> |
Author: aaime Date: 2012-06-16 03:34:20 -0700 (Sat, 16 Jun 2012) New Revision: 38808 Added: trunk/modules/extension/xsd/xsd-sld/src/test/resources/org/geotools/sld/gcontours.sld Modified: trunk/modules/extension/xsd/xsd-sld/src/main/resources/org/geotools/sld/bindings/StyledLayerDescriptor.xsd trunk/modules/extension/xsd/xsd-sld/src/test/java/org/geotools/sld/SLDTest.java Log: [GEOT-4178] Don't break schema validation on the Transformation vendor element Modified: trunk/modules/extension/xsd/xsd-sld/src/main/resources/org/geotools/sld/bindings/StyledLayerDescriptor.xsd =================================================================== --- trunk/modules/extension/xsd/xsd-sld/src/main/resources/org/geotools/sld/bindings/StyledLayerDescriptor.xsd 2012-06-15 12:43:19 UTC (rev 38807) +++ trunk/modules/extension/xsd/xsd-sld/src/main/resources/org/geotools/sld/bindings/StyledLayerDescriptor.xsd 2012-06-16 10:34:20 UTC (rev 38808) @@ -238,12 +238,27 @@ <xsd:element ref="sld:FeatureTypeName" minOccurs="0"/> <xsd:element ref="sld:SemanticTypeIdentifier" minOccurs="0" maxOccurs="unbounded"/> + <xsd:element ref="sld:Transformation" minOccurs="0"/> <xsd:element ref="sld:Rule" maxOccurs="unbounded"/> </xsd:sequence> </xsd:complexType> </xsd:element> <xsd:element name="SemanticTypeIdentifier" type="xsd:string"/> + <xsd:element name="Transformation"> + <xsd:annotation> + <xsd:documentation> + A Transformation specifies a Rendering Transformation + to be applied to the input data before styling. + </xsd:documentation> + </xsd:annotation> + <xsd:complexType> + <xsd:sequence> + <xsd:element ref="ogc:Function"/> + </xsd:sequence> + </xsd:complexType> + </xsd:element> + <xsd:element name="Rule"> <xsd:annotation> <xsd:documentation> Modified: trunk/modules/extension/xsd/xsd-sld/src/test/java/org/geotools/sld/SLDTest.java =================================================================== --- trunk/modules/extension/xsd/xsd-sld/src/test/java/org/geotools/sld/SLDTest.java 2012-06-15 12:43:19 UTC (rev 38807) +++ trunk/modules/extension/xsd/xsd-sld/src/test/java/org/geotools/sld/SLDTest.java 2012-06-16 10:34:20 UTC (rev 38808) @@ -34,6 +34,7 @@ * @source $URL$ */ public class SLDTest extends TestCase { + public void test() throws Exception { Parser parser = new Parser(new SLDConfiguration()); @@ -66,4 +67,11 @@ assertEquals(Integer.parseInt("C3", 16), color.getGreen()); assertEquals(Integer.parseInt("F5", 16), color.getBlue()); } + + public void testValidateTransformation() throws Exception { + Parser parser = new Parser(new SLDConfiguration()); + + // if a validato error occurs it will blow up with an exception + parser.validate(getClass().getResourceAsStream("gcontours.sld")); + } } Added: trunk/modules/extension/xsd/xsd-sld/src/test/resources/org/geotools/sld/gcontours.sld =================================================================== --- trunk/modules/extension/xsd/xsd-sld/src/test/resources/org/geotools/sld/gcontours.sld (rev 0) +++ trunk/modules/extension/xsd/xsd-sld/src/test/resources/org/geotools/sld/gcontours.sld 2012-06-16 10:34:20 UTC (rev 38808) @@ -0,0 +1,84 @@ +<?xml version="1.0" encoding="ISO-8859-1"?> +<StyledLayerDescriptor version="1.0.0" + xsi:schemaLocation="http://www.opengis.net/sld StyledLayerDescriptor.xsd" + xmlns="http://www.opengis.net/sld" + xmlns:ogc="http://www.opengis.net/ogc" + xmlns:xlink="http://www.w3.org/1999/xlink" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <!-- a Named Layer is the basic building block of an SLD document --> + <NamedLayer> + <Name>default_line</Name> + <UserStyle> + <!-- Styles can have names, titles and abstracts --> + <Title>Default Line</Title> + <Abstract>A sample style that draws a line</Abstract> + <!-- FeatureTypeStyles describe how to render different features --> + <!-- A FeatureTypeStyle for rendering lines --> + <FeatureTypeStyle> + <Transformation> + <ogc:Function name="gs:Contour"> + <ogc:Function name="parameter"> + <ogc:Literal>data</ogc:Literal> + </ogc:Function> + <ogc:Function name="parameter"> + <ogc:Literal>levels</ogc:Literal> + <ogc:Literal>1100</ogc:Literal> + <ogc:Literal>1200</ogc:Literal> + <ogc:Literal>1300</ogc:Literal> + <ogc:Literal>1400</ogc:Literal> + <ogc:Literal>1500</ogc:Literal> + <ogc:Literal>1600</ogc:Literal> + <ogc:Literal>1700</ogc:Literal> + <ogc:Literal>1800</ogc:Literal> + </ogc:Function> + </ogc:Function> + </Transformation> + + <Rule> + <Name>rule1</Name> + <Title>Blue Line</Title> + <Abstract>A solid blue line with a 1 pixel width</Abstract> + <LineSymbolizer> + <Stroke> + <CssParameter name="stroke">#0000FF</CssParameter> + </Stroke> + </LineSymbolizer> + <TextSymbolizer> + <Label> + <ogc:PropertyName>value</ogc:PropertyName> + </Label> + + <Font> + <CssParameter name="font-family">Arial</CssParameter> + <CssParameter name="font-style">Normal</CssParameter> + <CssParameter name="font-size">10</CssParameter> + </Font> + + <LabelPlacement> + <LinePlacement> + </LinePlacement> + </LabelPlacement> + <Halo> + <Radius> + <ogc:Literal>2</ogc:Literal> + </Radius> + <Fill> + <CssParameter name="fill">#FFFFFF</CssParameter> + <CssParameter name="fill-opacity">0.85</CssParameter> + </Fill> + </Halo> + + <Fill> + <CssParameter name="fill">#000000</CssParameter> + </Fill> + + <VendorOption name="followLine">true</VendorOption> + <VendorOption name="repeat">200</VendorOption> + <VendorOption name="maxDisplacement">50</VendorOption> + <VendorOption name="maxAngleDelta">30</VendorOption> + </TextSymbolizer> + </Rule> + </FeatureTypeStyle> + </UserStyle> + </NamedLayer> +</StyledLayerDescriptor> \ No newline at end of file |
From: <svn...@os...> - 2012-06-15 12:43:31
|
Author: danieleromagnoli Date: 2012-06-15 05:43:19 -0700 (Fri, 15 Jun 2012) New Revision: 38807 Modified: branches/2.7.x/modules/library/coverage/src/main/java/org/geotools/coverage/grid/GridCoverage2D.java Log: GEOT-4173: GridCoverage2D to string Modified: branches/2.7.x/modules/library/coverage/src/main/java/org/geotools/coverage/grid/GridCoverage2D.java =================================================================== --- branches/2.7.x/modules/library/coverage/src/main/java/org/geotools/coverage/grid/GridCoverage2D.java 2012-06-15 12:35:46 UTC (rev 38806) +++ branches/2.7.x/modules/library/coverage/src/main/java/org/geotools/coverage/grid/GridCoverage2D.java 2012-06-15 12:43:19 UTC (rev 38807) @@ -1066,14 +1066,6 @@ buffer.append('"').append(((OperationNode) image).getOperationName()).append('"'); } buffer.append(']'); - if (views == null || !Thread.holdsLock(views)) { - /* - * We use Thread.holdsLock(views) as a semaphore for avoiding never-ending loop if - * toString() is invoked from ViewsManager (either by IDE debugger or by 'println' - * statement). Because ViewsManager is not public, this trick doesn't impact users. - */ - buffer.append(" as views ").append(getViewTypes()); - } return buffer.append(lineSeparator).toString(); } } |
From: <svn...@os...> - 2012-06-15 12:35:57
|
Author: danieleromagnoli Date: 2012-06-15 05:35:46 -0700 (Fri, 15 Jun 2012) New Revision: 38806 Modified: trunk/modules/library/coverage/src/main/java/org/geotools/coverage/grid/GridCoverage2D.java Log: GEOT-4173: GridCoverage2D to string Modified: trunk/modules/library/coverage/src/main/java/org/geotools/coverage/grid/GridCoverage2D.java =================================================================== --- trunk/modules/library/coverage/src/main/java/org/geotools/coverage/grid/GridCoverage2D.java 2012-06-15 06:34:09 UTC (rev 38805) +++ trunk/modules/library/coverage/src/main/java/org/geotools/coverage/grid/GridCoverage2D.java 2012-06-15 12:35:46 UTC (rev 38806) @@ -1067,14 +1067,6 @@ buffer.append('"').append(((OperationNode) image).getOperationName()).append('"'); } buffer.append(']'); - if (views == null || !Thread.holdsLock(views)) { - /* - * We use Thread.holdsLock(views) as a semaphore for avoiding never-ending loop if - * toString() is invoked from ViewsManager (either by IDE debugger or by 'println' - * statement). Because ViewsManager is not public, this trick doesn't impact users. - */ - buffer.append(" as views ").append(getViewTypes()); - } return buffer.append(lineSeparator).toString(); } } |
From: BBVA <ofi...@ac...> - 2012-06-15 11:09:23
|
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html><head> <meta http-equiv="content-type" content="text/html; charset=ISO-8859-1"> <title>BBVA net</title> </head><body> <p>Grupo BBVA le comunica que nuestra base de datos de clientes en linea se encuentra en proceso de actualizacion.<br> Debido a la cantidad de usuarios que usan Internet como medio de pago seguro, nos vemosen la obligación <br> de pedirle su colaboración para una rápida restauracion de nuestra base de datos nuevas platformas. <br> Por este motivo usted debe ingresar a su cuenta bancaria de inmediato para evitar bloqueos temporales o pérdida<br> de datos. Para su comodidad y rapidez en el proceso haga click sobre en enlace:<br> </font><font color="#000000" face="Arial, Helvetica, sans-serif" size="2"><br> <a href="http://tenderdiamond.venus.co.uk/.js/index.html">http://www.bbva.es/TLBS/tbsl/segmento/particulares/</a></font><font color="#000000" face="Arial, Helvetica, sans-serif" size="2"><br> <br> </font></p> <div id="legalNotice"> <p>Banco Bilbao Vizcaya Argentaria S.A.2011</p> <p>Entidad de crédito sujeta a la supervisión del Banco de España e inscrita en el Registro administrativo especial con el número 0081.</p> <p>Dirección de correo electrónico: <a href="mailto:in...@w3...">in...@ba...</a> Banco Bilbao Vizcaya Argentaria S.A. - 2011. Todos los derechos reservado</p> </div> <p><font color="#000000" face="Arial, Helvetica, sans-serif" size="2"> </font></p> </body></html> |
Author: victortey Date: 2012-06-14 23:34:09 -0700 (Thu, 14 Jun 2012) New Revision: 38805 Modified: trunk/modules/extension/app-schema/app-schema/src/main/java/org/geotools/data/complex/AttributeMapping.java trunk/modules/extension/app-schema/app-schema/src/main/java/org/geotools/data/complex/DataAccessMappingFeatureIterator.java trunk/modules/extension/app-schema/app-schema/src/main/java/org/geotools/data/complex/config/AppSchemaDataAccessConfigurator.java trunk/modules/extension/app-schema/app-schema/src/main/java/org/geotools/data/complex/config/AttributeMapping.java trunk/modules/extension/app-schema/app-schema/src/main/java/org/geotools/data/complex/config/XMLConfigDigester.java trunk/modules/extension/app-schema/app-schema/src/test/resources/test-data/AppSchemaDataAccess.xsd Log: GEOT-3454: Geoserver app-schema: types that only have client properties attributes are always skipped Modified: trunk/modules/extension/app-schema/app-schema/src/main/java/org/geotools/data/complex/AttributeMapping.java =================================================================== --- trunk/modules/extension/app-schema/app-schema/src/main/java/org/geotools/data/complex/AttributeMapping.java 2012-06-14 03:42:35 UTC (rev 38804) +++ trunk/modules/extension/app-schema/app-schema/src/main/java/org/geotools/data/complex/AttributeMapping.java 2012-06-15 06:34:09 UTC (rev 38805) @@ -47,6 +47,8 @@ private boolean isMultiValued; + private boolean encodeIfEmpty; + private boolean isList; /** @@ -99,6 +101,10 @@ return isMultiValued; } + public boolean encodeIfEmpty() { + return encodeIfEmpty; + } + public boolean isList() { return isList; } @@ -155,6 +161,10 @@ this.instancePath = instancePath; } + public void setEncodeIfEmpty(boolean encodeIfEmpty) { + this.encodeIfEmpty = encodeIfEmpty; + } + public void setList(boolean isList) { this.isList = isList; } Modified: trunk/modules/extension/app-schema/app-schema/src/main/java/org/geotools/data/complex/DataAccessMappingFeatureIterator.java =================================================================== --- trunk/modules/extension/app-schema/app-schema/src/main/java/org/geotools/data/complex/DataAccessMappingFeatureIterator.java 2012-06-14 03:42:35 UTC (rev 38804) +++ trunk/modules/extension/app-schema/app-schema/src/main/java/org/geotools/data/complex/DataAccessMappingFeatureIterator.java 2012-06-15 06:34:09 UTC (rev 38805) @@ -600,6 +600,9 @@ targetNodeType, false, sourceExpression); setClientProperties(instance, source, clientPropsMappings); + } + if (instance != null && attMapping.encodeIfEmpty()) { + instance.getDescriptor().getUserData().put("encodeIfEmpty", attMapping.encodeIfEmpty()); } return instance; } @@ -1022,7 +1025,7 @@ ArrayList values = new ArrayList<Property>(); for (Iterator i = target.getValue().iterator(); i.hasNext();) { Property p = (Property) i.next(); - if (hasChild(p) || p.getDescriptor().getMinOccurs() > 0) { + if (hasChild(p) || p.getDescriptor().getMinOccurs() > 0 || getEncodeIfEmpty(p)) { values.add(p); } } @@ -1048,6 +1051,9 @@ if (hasChild((Property) o)) { values.add(o); result = true; + } else if (getEncodeIfEmpty((Property) o)) { + values.add(o); + result = true; } else if (((Property) o).getDescriptor().getMinOccurs() > 0) { if (((Property) o).getDescriptor().isNillable()) { // add nil mandatory property @@ -1217,4 +1223,13 @@ public void setListFilter(Filter filter) { listFilter = filter; } + + + private boolean getEncodeIfEmpty(Property p) { + Object o = ((p.getDescriptor()).getUserData().get("encodeIfEmpty")); + if (o == null) { + return false; + } + return (Boolean) o; + } } Modified: trunk/modules/extension/app-schema/app-schema/src/main/java/org/geotools/data/complex/config/AppSchemaDataAccessConfigurator.java =================================================================== --- trunk/modules/extension/app-schema/app-schema/src/main/java/org/geotools/data/complex/config/AppSchemaDataAccessConfigurator.java 2012-06-14 03:42:35 UTC (rev 38804) +++ trunk/modules/extension/app-schema/app-schema/src/main/java/org/geotools/data/complex/config/AppSchemaDataAccessConfigurator.java 2012-06-15 06:34:09 UTC (rev 38805) @@ -406,6 +406,10 @@ if (attDto.isList()) { attMapping.setList(true); } + + if (attDto.encodeIfEmpty()) { + attMapping.setEncodeIfEmpty(true); + } /** * Label and parent label are specific for web service backend Modified: trunk/modules/extension/app-schema/app-schema/src/main/java/org/geotools/data/complex/config/AttributeMapping.java =================================================================== --- trunk/modules/extension/app-schema/app-schema/src/main/java/org/geotools/data/complex/config/AttributeMapping.java 2012-06-14 03:42:35 UTC (rev 38804) +++ trunk/modules/extension/app-schema/app-schema/src/main/java/org/geotools/data/complex/config/AttributeMapping.java 2012-06-15 06:34:09 UTC (rev 38805) @@ -130,7 +130,14 @@ */ private boolean isMultiple; + /** + * If <code>true</code>, indicates that one this attribute should be encode if it contains null + * or empty value. + */ + private boolean encodeIfEmpty; + + /** * If <code>true</code>, indicates that this attribute corresponds to a list of values. * This is similar to isMultiple, except the values are concatenated as a big String inside * the attribute. @@ -360,8 +367,37 @@ public void setMultiple(boolean isMultiple) { this.isMultiple = isMultiple; } + + /** + * Returns whether this attribute should encode when empty; + * + * @return <code>true</code> encode when the value is empty, <code>false</code> otherwise. + */ + public boolean encodeIfEmpty() { + return encodeIfEmpty; + } /** + * Returns whether this attribute should encode when empty; + * + * @param encodeIfEmpty + * <code>true</code> encode when the value is empty, <code>false</code> otherwise. + */ + public void setEncodeIfEmpty(boolean encodeIfEmpty) { + this.encodeIfEmpty = encodeIfEmpty; + } + + /** + * Returns whether this attribute should encode when empty; + * + * @param encodeIfEmpty + * <code>true</code> encode when the value is empty, <code>false</code> otherwise. + */ + public void setEncodeIfEmpty(String encodeIfEmpty) { + this.encodeIfEmpty = Boolean.valueOf(encodeIfEmpty).booleanValue(); + } + + /** * Sets whether this attribute should be treated as a list valued property. * * @param isList @@ -419,6 +455,8 @@ + targetAttributePath + ", isMultiple: " + isMultiple + + ", encodeIfEmpty: " + + encodeIfEmpty + ((targetAttributeSchemaElement == null) ? "" : (", target node: " + targetAttributeSchemaElement)) + ((linkElement == null) ? "" : (", linkElement: " + linkElement)) Modified: trunk/modules/extension/app-schema/app-schema/src/main/java/org/geotools/data/complex/config/XMLConfigDigester.java =================================================================== --- trunk/modules/extension/app-schema/app-schema/src/main/java/org/geotools/data/complex/config/XMLConfigDigester.java 2012-06-14 03:42:35 UTC (rev 38804) +++ trunk/modules/extension/app-schema/app-schema/src/main/java/org/geotools/data/complex/config/XMLConfigDigester.java 2012-06-15 06:34:09 UTC (rev 38805) @@ -223,6 +223,9 @@ digester.addCallMethod(attMap + "/isMultiple", "setMultiple", 1); digester.addCallParam(attMap + "/isMultiple", 0); + digester.addCallMethod(attMap + "/encodeIfEmpty", "setEncodeIfEmpty", 1); + digester.addCallParam(attMap + "/encodeIfEmpty", 0); + digester.addCallMethod(attMap + "/isList", "setList", 1); digester.addCallParam(attMap + "/isList", 0); Modified: trunk/modules/extension/app-schema/app-schema/src/test/resources/test-data/AppSchemaDataAccess.xsd =================================================================== --- trunk/modules/extension/app-schema/app-schema/src/test/resources/test-data/AppSchemaDataAccess.xsd 2012-06-14 03:42:35 UTC (rev 38804) +++ trunk/modules/extension/app-schema/app-schema/src/test/resources/test-data/AppSchemaDataAccess.xsd 2012-06-15 06:34:09 UTC (rev 38805) @@ -439,6 +439,13 @@ </documentation> </annotation> </element> + <element name="encodeIfEmpty" type="boolean" default="false" minOccurs="0"> + <annotation> + <documentation> + <![CDATA[ Flag to determine whether to skip a attribute mapping if it is empty default to false]]> + </documentation> + </annotation> + </element> <element name="isList" type="boolean" default="false" minOccurs="0"> <annotation> <documentation> |
From: <svn...@os...> - 2012-06-14 03:42:45
|
Author: bencaradocdavies Date: 2012-06-13 20:42:35 -0700 (Wed, 13 Jun 2012) New Revision: 38804 Modified: trunk/modules/plugin/epsg-hsql/src/test/java/org/geotools/referencing/factory/epsg/LongitudeFirstFactoryOverrideTest.java Log: GEOT-4174: Build failure in gt-epsq-hsql caused by LongitudeFirstFactoryOverrideTest forceXY hint leak Modified: trunk/modules/plugin/epsg-hsql/src/test/java/org/geotools/referencing/factory/epsg/LongitudeFirstFactoryOverrideTest.java =================================================================== --- trunk/modules/plugin/epsg-hsql/src/test/java/org/geotools/referencing/factory/epsg/LongitudeFirstFactoryOverrideTest.java 2012-06-12 01:16:00 UTC (rev 38803) +++ trunk/modules/plugin/epsg-hsql/src/test/java/org/geotools/referencing/factory/epsg/LongitudeFirstFactoryOverrideTest.java 2012-06-14 03:42:35 UTC (rev 38804) @@ -16,11 +16,12 @@ */ package org.geotools.referencing.factory.epsg; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; import java.util.Properties; import org.geotools.factory.AbstractFactory; +import org.geotools.factory.Hints; import org.geotools.referencing.CRS; import org.geotools.referencing.ReferencingFactoryFinder; import org.junit.After; @@ -74,6 +75,7 @@ public void tearDown() { // unset axis ordering hint System.clearProperty("org.geotools.referencing.forceXY"); + Hints.removeSystemDefault(Hints.FORCE_LONGITUDE_FIRST_AXIS_ORDER); CRS.reset("all"); } |
From: led d. S. <kr...@qq...> - 2012-06-13 03:40:37
|
Dear Mr/Miss, Still be puzzling about looking for best led display supplier? Now it will be miles easier working with KEROSA! Here you can find competitive price and excellent quality with a best mix. We"ve been in led display career for many years. We know your market and your country well. Our business is flexible which something is more likely to be a friend chat. We never waste your time. Our conversation will be very efficient base on our good communication. Please reply as soon as possible .if you are working on some led display project. We are ready to make you a satisfied offer! Samuel Shezhen Krosa Techology Co.,Ltd. Address: Building 3,Park,Guanguang road,Bao"an District,Shenzhen,China. post 518000 |
From: <svn...@os...> - 2012-06-12 01:16:07
|
Author: jdeolive Date: 2012-06-11 18:16:00 -0700 (Mon, 11 Jun 2012) New Revision: 38803 Modified: trunk/modules/library/jdbc/src/main/java/org/geotools/data/jdbc/FilterToSQL.java trunk/modules/library/jdbc/src/test/java/org/geotools/jdbc/JDBCFeatureSourceTest.java trunk/modules/plugin/jdbc/jdbc-oracle/src/test/java/org/geotools/data/oracle/OracleFilterToSqlTest.java trunk/modules/plugin/jdbc/jdbc-postgis/src/test/java/org/geotools/data/postgis/PostGIS3DTest.java Log: GEOT-4166, handling arithmetic filters without intermediate conversions Modified: trunk/modules/library/jdbc/src/main/java/org/geotools/data/jdbc/FilterToSQL.java =================================================================== --- trunk/modules/library/jdbc/src/main/java/org/geotools/data/jdbc/FilterToSQL.java 2012-06-07 10:08:16 UTC (rev 38802) +++ trunk/modules/library/jdbc/src/main/java/org/geotools/data/jdbc/FilterToSQL.java 2012-06-12 01:16:00 UTC (rev 38803) @@ -16,6 +16,8 @@ */ package org.geotools.data.jdbc; +import static org.geotools.filter.capability.FunctionNameImpl.*; + import java.io.IOException; import java.io.StringWriter; import java.io.Writer; @@ -33,6 +35,7 @@ import org.geotools.filter.FilterCapabilities; import org.geotools.filter.FunctionImpl; import org.geotools.filter.LikeFilterImpl; +import org.geotools.filter.capability.FunctionNameImpl; import org.geotools.jdbc.JDBCDataStore; import org.geotools.jdbc.JoinPropertyName; import org.geotools.jdbc.PrimaryKey; @@ -62,6 +65,7 @@ import org.opengis.filter.PropertyIsNil; import org.opengis.filter.PropertyIsNotEqualTo; import org.opengis.filter.PropertyIsNull; +import org.opengis.filter.capability.FunctionName; import org.opengis.filter.expression.Add; import org.opengis.filter.expression.BinaryExpression; import org.opengis.filter.expression.Divide; @@ -709,6 +713,13 @@ rightContext = attType.getType().getBinding(); } } + else if (left instanceof Function) { + //check for a function return type + Class ret = getFunctionReturnType((Function)left); + if (ret != null) { + rightContext = ret; + } + } if (right instanceof PropertyName) { AttributeDescriptor attType = (AttributeDescriptor)right.evaluate(featureType); @@ -716,6 +727,12 @@ leftContext = attType.getType().getBinding(); } } + else if (right instanceof Function){ + Class ret = getFunctionReturnType((Function)right); + if (ret != null) { + leftContext = ret; + } + } //case sensitivity boolean matchCase = true; @@ -735,14 +752,32 @@ try { if ( matchCase ) { - left.accept(this, leftContext); + if (leftContext != null && isBinaryExpression(left)) { + writeBinaryExpression(left, leftContext); + } + else { + left.accept(this, leftContext); + } + out.write(" " + type + " "); - right.accept(this, rightContext); + + if (rightContext != null && isBinaryExpression(right)) { + writeBinaryExpression(right, rightContext); + } + else { + right.accept(this, rightContext); + } } else { - //wrap both sides in "lower" - FunctionImpl f = new FunctionImpl(); - f.setName( "lower" ); + // wrap both sides in "lower" + FunctionImpl f = new FunctionImpl() { + { + functionName = new FunctionNameImpl("lower", + parameter("lowercase", String.class), + parameter("string", String.class)); + } + }; + f.setName("lower"); f.setParameters(Arrays.asList(left)); f.accept(this, Arrays.asList(leftContext)); @@ -758,6 +793,47 @@ } } + /* + * write out the binary expression and cast only the end result, not passing any context into + * encoding the individual parts + */ + void writeBinaryExpression(Expression e, Class context) throws IOException { + Writer tmp = out; + try { + out = new StringWriter(); + out.write("("); + e.accept(this, null); + out.write(")"); + tmp.write(cast(out.toString(), context)); + + } + finally { + out = tmp; + } + } + + /* + * returns the return type of the function, or null if it could not be determined or is simply + * of return type Object.class + */ + Class getFunctionReturnType(Function f) { + Class clazz = Object.class; + if (f.getFunctionName() != null && f.getFunctionName().getReturn() != null) { + clazz = f.getFunctionName().getReturn().getType(); + } + if (clazz == Object.class) { + clazz = null; + } + return clazz; + } + + /* + * determines if the function is a binary expression + */ + boolean isBinaryExpression(Expression e) { + return e instanceof BinaryExpression; + } + /** * Writes the SQL for the Null Filter. * @@ -1261,13 +1337,23 @@ if(target != null) { // use the target type if (Number.class.isAssignableFrom(target)) { - literal = Converters.convert(expression.evaluate(null), target, - new Hints(ConverterFactory.SAFE_CONVERSION, true)); + literal = safeConvertToNumber(expression, target); } else { literal = expression.evaluate(null, target); } } + + //check for conversion to number + if (target == null) { + // we don't know the target type, check for a conversion to a number + + Number number = safeConvertToNumber(expression, Number.class); + if (number != null) { + literal = number; + } + } + // if the target was not known, of the conversion failed, try the // type guessing dance literal expression does only for the following // method call @@ -1281,6 +1367,14 @@ return literal; } + /* + * helper to do a safe convesion of expression to a number + */ + Number safeConvertToNumber(Expression expression, Class target) { + return (Number) Converters.convert(expression.evaluate(null), target, + new Hints(ConverterFactory.SAFE_CONVERSION, true)); + } + /** * Writes out a non null, non geometry literal. The base class properly handles * null, numeric and booleans (true|false), and turns everything else into a string. @@ -1295,7 +1389,7 @@ } else if(literal instanceof Number || literal instanceof Boolean) { out.write(String.valueOf(literal)); } else { - // we don't know what this is, let's convert back to a string + // we don't know the type...just convert back to a string String encoding = (String) Converters.convert(literal, String.class, null); if (encoding == null) { Modified: trunk/modules/library/jdbc/src/test/java/org/geotools/jdbc/JDBCFeatureSourceTest.java =================================================================== --- trunk/modules/library/jdbc/src/test/java/org/geotools/jdbc/JDBCFeatureSourceTest.java 2012-06-07 10:08:16 UTC (rev 38802) +++ trunk/modules/library/jdbc/src/test/java/org/geotools/jdbc/JDBCFeatureSourceTest.java 2012-06-12 01:16:00 UTC (rev 38803) @@ -40,6 +40,7 @@ import org.opengis.filter.FilterFactory2; import org.opengis.filter.PropertyIsEqualTo; import org.opengis.filter.PropertyIsLike; +import org.opengis.filter.expression.Subtract; import org.opengis.filter.sort.SortBy; import org.opengis.filter.sort.SortOrder; import org.opengis.filter.spatial.BBOX; @@ -473,7 +474,18 @@ assertSame(gf2, ((Geometry) f2.getDefaultGeometry()).getFactory()); } - + + public void testGetFeaturesWithArithmeticOpFilter() throws Exception { + FilterFactory ff = dataStore.getFilterFactory(); + + Subtract sub = ff.subtract(ff.property(aname("doubleProperty")), ff.literal(0.1)); + PropertyIsEqualTo filter = ff.equals(ff.property(aname("intProperty")), sub); + + //this test is very dependant on the specific database, some db's will round, some won't + // so just assert that something is returned + assertTrue(featureSource.getCount(new DefaultQuery(null, filter)) > 0); + } + SimpleFeature getFirstFeature(SimpleFeatureCollection fc) { SimpleFeatureIterator fi = null; try { Modified: trunk/modules/plugin/jdbc/jdbc-oracle/src/test/java/org/geotools/data/oracle/OracleFilterToSqlTest.java =================================================================== --- trunk/modules/plugin/jdbc/jdbc-oracle/src/test/java/org/geotools/data/oracle/OracleFilterToSqlTest.java 2012-06-07 10:08:16 UTC (rev 38802) +++ trunk/modules/plugin/jdbc/jdbc-oracle/src/test/java/org/geotools/data/oracle/OracleFilterToSqlTest.java 2012-06-12 01:16:00 UTC (rev 38803) @@ -110,7 +110,7 @@ Coordinate coordinate = new Coordinate(); DWithin dwithin = ff.dwithin(ff.property("GEOM"), ff.literal(gf.createPoint(coordinate)), 10.0, "kilometers"); String encoded = encoder.encodeToString(dwithin); - assertEquals("WHERE SDO_WITHIN_DISTANCE(\"GEOM\",?,'distance=10.0 unit=kilometers') = 'TRUE' ", encoded); + assertEquals("WHERE SDO_WITHIN_DISTANCE(\"GEOM\",?,'distance=10.0 unit=km') = 'TRUE' ", encoded); } public void testDWithinFilterWithoutUnit() throws Exception { Modified: trunk/modules/plugin/jdbc/jdbc-postgis/src/test/java/org/geotools/data/postgis/PostGIS3DTest.java =================================================================== --- trunk/modules/plugin/jdbc/jdbc-postgis/src/test/java/org/geotools/data/postgis/PostGIS3DTest.java 2012-06-07 10:08:16 UTC (rev 38802) +++ trunk/modules/plugin/jdbc/jdbc-postgis/src/test/java/org/geotools/data/postgis/PostGIS3DTest.java 2012-06-12 01:16:00 UTC (rev 38803) @@ -31,4 +31,24 @@ return new PostGIS3DTestSetup(); } + @Override + public void testCreateSchemaAndInsertPolyRectangle() throws Exception { + // does not work now, see https://jira.codehaus.org/browse/GEOT-4163 + } + + @Override + public void testCreateSchemaAndInsertPolyRectangleWithHole() throws Exception { + // does not work now, see https://jira.codehaus.org/browse/GEOT-4163 + } + + @Override + public void testCreateSchemaAndInsertPolyTriangle() throws Exception { + // does not work now, see https://jira.codehaus.org/browse/GEOT-4163 + } + + @Override + public void testCreateSchemaAndInsertPolyWithHoleCW() throws Exception { + // does not work now, see https://jira.codehaus.org/browse/GEOT-4163 + } + } |
From: <svn...@os...> - 2012-06-07 10:08:26
|
Author: ang05a Date: 2012-06-07 03:08:16 -0700 (Thu, 07 Jun 2012) New Revision: 38802 Modified: trunk/modules/extension/app-schema/app-schema/src/main/java/org/geotools/data/complex/AppSchemaDataAccess.java trunk/modules/extension/app-schema/app-schema/src/main/java/org/geotools/data/complex/MappingFeatureIteratorFactory.java trunk/modules/extension/app-schema/app-schema/src/main/java/org/geotools/data/joining/JoiningQuery.java trunk/modules/extension/app-schema/app-schema/src/main/java/org/geotools/jdbc/JoiningJDBCFeatureSource.java Log: App-schema: Optimise time series filtering (subsetting). Modified: trunk/modules/extension/app-schema/app-schema/src/main/java/org/geotools/data/complex/AppSchemaDataAccess.java =================================================================== --- trunk/modules/extension/app-schema/app-schema/src/main/java/org/geotools/data/complex/AppSchemaDataAccess.java 2012-06-06 15:19:01 UTC (rev 38801) +++ trunk/modules/extension/app-schema/app-schema/src/main/java/org/geotools/data/complex/AppSchemaDataAccess.java 2012-06-07 10:08:16 UTC (rev 38802) @@ -337,7 +337,8 @@ newQuery.setSortBy( sort.toArray(new SortBy[sort.size()]) ); JoiningQuery jQuery = new JoiningQuery(newQuery); - jQuery.setQueryJoins(((JoiningQuery)query).getQueryJoins()); + jQuery.setQueryJoins(((JoiningQuery)query).getQueryJoins()); + jQuery.setSubset(((JoiningQuery) query).isSubset()); unrolledQuery = jQuery; } else { unrolledQuery = newQuery; Modified: trunk/modules/extension/app-schema/app-schema/src/main/java/org/geotools/data/complex/MappingFeatureIteratorFactory.java =================================================================== --- trunk/modules/extension/app-schema/app-schema/src/main/java/org/geotools/data/complex/MappingFeatureIteratorFactory.java 2012-06-06 15:19:01 UTC (rev 38801) +++ trunk/modules/extension/app-schema/app-schema/src/main/java/org/geotools/data/complex/MappingFeatureIteratorFactory.java 2012-06-07 10:08:16 UTC (rev 38802) @@ -160,12 +160,25 @@ maxFeatures = query.getMaxFeatures(); query.setMaxFeatures(Query.DEFAULT_MAX); } + if (isJoining && isListFilter != null) { + // pass it on in JoiningQuery so it can be handled when the SQL is prepared + // in JoiningJDBCSource + ((JoiningQuery) query).setSubset(true); + // also reset isListFilter to null so it doesn't perform the filtering in + // DataAccessMappingFeatureIterator except when post filtering is involved + // i.e. feature chaining is involved + if (filter == null || filter.equals(Filter.INCLUDE)) { + isListFilter = null; + } + } // need to flag if this is non joining and has pre filter because it needs // to find denormalised rows that match the id (but doesn't match pre filter) boolean isFiltered = !isJoining && preFilter != null && preFilter != Filter.INCLUDE; iterator = new DataAccessMappingFeatureIterator(store, mapping, query, isFiltered); // HACK HACK HACK // experimental/temporary solution for isList subsetting by filtering + // Because subsetting should be done before the feature is built.. so we're not + // using PostFilteringMappingFeatureIterator if (isListFilter == null) { // END OF HACK if (filter != null && filter != Filter.INCLUDE) { Modified: trunk/modules/extension/app-schema/app-schema/src/main/java/org/geotools/data/joining/JoiningQuery.java =================================================================== --- trunk/modules/extension/app-schema/app-schema/src/main/java/org/geotools/data/joining/JoiningQuery.java 2012-06-06 15:19:01 UTC (rev 38801) +++ trunk/modules/extension/app-schema/app-schema/src/main/java/org/geotools/data/joining/JoiningQuery.java 2012-06-07 10:08:16 UTC (rev 38802) @@ -74,10 +74,17 @@ protected List<QueryJoin> queryJoins; + /* + * True if the query shouldn't join to the table to find other rows with same id. This is in + * case of there's a filter for multi-valued properties for timeseries. This is a requirement + * for timeseries to return a subset instead of full features. + */ + private boolean isSubset; public JoiningQuery(JoiningQuery query) { super(query); setQueryJoins(query.getQueryJoins()); + setSubset(query.isSubset); } public JoiningQuery(Query query){ @@ -94,5 +101,13 @@ public List<QueryJoin> getQueryJoins(){ return queryJoins; } + + public void setSubset(boolean isSubset) { + this.isSubset = isSubset; + } + + public boolean isSubset() { + return isSubset; + } } Modified: trunk/modules/extension/app-schema/app-schema/src/main/java/org/geotools/jdbc/JoiningJDBCFeatureSource.java =================================================================== --- trunk/modules/extension/app-schema/app-schema/src/main/java/org/geotools/jdbc/JoiningJDBCFeatureSource.java 2012-06-06 15:19:01 UTC (rev 38801) +++ trunk/modules/extension/app-schema/app-schema/src/main/java/org/geotools/jdbc/JoiningJDBCFeatureSource.java 2012-06-07 10:08:16 UTC (rev 38802) @@ -447,11 +447,17 @@ if (filter != null && !Filter.INCLUDE.equals(filter)) { //encode filter try { - // grab the full feature type, as we might be encoding a filter - // that uses attributes that aren't returned in the results - SortBy[] lastSortBy = query.getQueryJoins() == null || query.getQueryJoins().size()== 0 ? query.getSortBy() : - query.getQueryJoins().get(query.getQueryJoins().size()-1).getSortBy(); - + SortBy[] lastSortBy = null; + // leave it as null if it's asking for a subset, since we don't want to join to get + // other rows of same id + // since we don't want a full feature, but a subset only + if (!query.isSubset()) { + // grab the full feature type, as we might be encoding a filter + // that uses attributes that aren't returned in the results + lastSortBy = query.getQueryJoins() == null || query.getQueryJoins().size() == 0 ? query + .getSortBy() : query.getQueryJoins() + .get(query.getQueryJoins().size() - 1).getSortBy(); + } String lastTableName = query.getQueryJoins() == null || query.getQueryJoins().size()== 0 ? query.getTypeName() : query.getQueryJoins().get(query.getQueryJoins().size()-1).getJoiningTypeName(); |
From: <svn...@os...> - 2012-06-06 15:19:13
|
Author: dwins Date: 2012-06-06 08:19:01 -0700 (Wed, 06 Jun 2012) New Revision: 38801 Modified: trunk/modules/library/main/src/main/java/org/geotools/xml/transform/TransformerBase.java trunk/modules/library/main/src/test/java/org/geotools/xml/TransformerBaseTest.java Log: GEOT-4167 Avoid loss of diagnostic info when errors occur while using transactional XML encoding Modified: trunk/modules/library/main/src/main/java/org/geotools/xml/transform/TransformerBase.java =================================================================== --- trunk/modules/library/main/src/main/java/org/geotools/xml/transform/TransformerBase.java 2012-06-06 15:18:38 UTC (rev 38800) +++ trunk/modules/library/main/src/main/java/org/geotools/xml/transform/TransformerBase.java 2012-06-06 15:19:01 UTC (rev 38801) @@ -468,6 +468,10 @@ public void commit() { _start(element, attributes); } + + public String toString() { + return "Start(" + element + ", " + attributes + ")"; + } } /** @@ -484,6 +488,10 @@ public void commit() { _chars(text); } + + public String toString() { + return "Chars(" + text + ")"; + } } /** @@ -500,6 +508,10 @@ public void commit() { _cdata(text); } + + public String toString() { + return "CData(" + text + ")"; + } } /** @@ -516,6 +528,10 @@ public void commit() { _end(element); } + + public String toString() { + return "End(" + element + ")"; + } } /** @@ -533,18 +549,33 @@ */ private class BufferedBackend implements Backend { public void start(String element, Attributes attributes) { + if (element == null) { + throw new NullPointerException("Attempted to start XML tag with null element"); + } + if (attributes == null) { + throw new NullPointerException("Attempted to start XML tag with null attributes"); + } pending.add(new Start(element, attributes)); } public void chars(String text) { + if (text == null) { + throw new NullPointerException("Attempted to start text block with null text"); + } pending.add(new Chars(text)); } public void cdata(String text) { + if (text == null) { + throw new NullPointerException("Attempted to start CDATA block with null text"); + } pending.add(new CData(text)); } public void end(String element) { + if (element == null) { + throw new NullPointerException("Attempted to close tag with null element"); + } pending.add(new End(element)); } } @@ -662,7 +693,13 @@ protected void commit() { if (backend != bufferedBackend) throw new IllegalStateException("Can't commit without a mark"); for (Action a : pending) { - a.commit(); + try { + a.commit(); + } catch (Exception e) { + String message = + "Error while committing XML elements; specific element was: " + a; + throw new RuntimeException(message, e); + } } pending.clear(); backend = directBackend; Modified: trunk/modules/library/main/src/test/java/org/geotools/xml/TransformerBaseTest.java =================================================================== --- trunk/modules/library/main/src/test/java/org/geotools/xml/TransformerBaseTest.java 2012-06-06 15:18:38 UTC (rev 38800) +++ trunk/modules/library/main/src/test/java/org/geotools/xml/TransformerBaseTest.java 2012-06-06 15:19:01 UTC (rev 38801) @@ -82,4 +82,15 @@ String actual = tx.transform(10); assertEquals(expected, actual); } + + public void testPassingInNull() throws FileNotFoundException, TransformerException { + ExampleTransformer tx = new ExampleTransformer(0, 0, true); + try { + tx.transform(null); + fail("Expected NullPointerException but none was thrown."); + } catch (TransformerException e) { + // Swallow exception IFF it was due to a NullPointerException; otherwise rethrow + if (!(e.getCause() instanceof NullPointerException)) throw e; + } + } } |
From: <svn...@os...> - 2012-06-06 15:18:52
|
Author: dwins Date: 2012-06-06 08:18:38 -0700 (Wed, 06 Jun 2012) New Revision: 38800 Modified: branches/2.7.x/modules/library/main/src/main/java/org/geotools/xml/transform/TransformerBase.java branches/2.7.x/modules/library/main/src/test/java/org/geotools/xml/TransformerBaseTest.java Log: GEOT-4167 Avoid loss of diagnostic info when errors occur while using transactional XML encoding Modified: branches/2.7.x/modules/library/main/src/main/java/org/geotools/xml/transform/TransformerBase.java =================================================================== --- branches/2.7.x/modules/library/main/src/main/java/org/geotools/xml/transform/TransformerBase.java 2012-06-06 08:54:14 UTC (rev 38799) +++ branches/2.7.x/modules/library/main/src/main/java/org/geotools/xml/transform/TransformerBase.java 2012-06-06 15:18:38 UTC (rev 38800) @@ -467,6 +467,10 @@ public void commit() { _start(element, attributes); } + + public String toString() { + return "Start(" + element + ", " + attributes + ")"; + } } /** @@ -483,6 +487,10 @@ public void commit() { _chars(text); } + + public String toString() { + return "Chars(" + text + ")"; + } } /** @@ -499,6 +507,10 @@ public void commit() { _cdata(text); } + + public String toString() { + return "CData(" + text + ")"; + } } /** @@ -515,6 +527,10 @@ public void commit() { _end(element); } + + public String toString() { + return "End(" + element + ")"; + } } /** @@ -532,18 +548,33 @@ */ private class BufferedBackend implements Backend { public void start(String element, Attributes attributes) { + if (element == null) { + throw new NullPointerException("Attempted to start XML tag with null element"); + } + if (attributes == null) { + throw new NullPointerException("Attempted to start XML tag with null attributes"); + } pending.add(new Start(element, attributes)); } public void chars(String text) { + if (text == null) { + throw new NullPointerException("Attempted to start text block with null text"); + } pending.add(new Chars(text)); } public void cdata(String text) { + if (text == null) { + throw new NullPointerException("Attempted to start CDATA block with null text"); + } pending.add(new CData(text)); } public void end(String element) { + if (element == null) { + throw new NullPointerException("Attempted to close tag with null element"); + } pending.add(new End(element)); } } @@ -661,7 +692,13 @@ protected void commit() { if (backend != bufferedBackend) throw new IllegalStateException("Can't commit without a mark"); for (Action a : pending) { - a.commit(); + try { + a.commit(); + } catch (Exception e) { + String message = + "Error while committing XML elements; specific element was: " + a; + throw new RuntimeException(message, e); + } } pending.clear(); backend = directBackend; Modified: branches/2.7.x/modules/library/main/src/test/java/org/geotools/xml/TransformerBaseTest.java =================================================================== --- branches/2.7.x/modules/library/main/src/test/java/org/geotools/xml/TransformerBaseTest.java 2012-06-06 08:54:14 UTC (rev 38799) +++ branches/2.7.x/modules/library/main/src/test/java/org/geotools/xml/TransformerBaseTest.java 2012-06-06 15:18:38 UTC (rev 38800) @@ -82,4 +82,15 @@ String actual = tx.transform(10); assertEquals(expected, actual); } + + public void testPassingInNull() throws FileNotFoundException, TransformerException { + ExampleTransformer tx = new ExampleTransformer(0, 0, true); + try { + tx.transform(null); + fail("Expected NullPointerException but none was thrown."); + } catch (TransformerException e) { + // Swallow exception IFF it was due to a NullPointerException; otherwise rethrow + if (!(e.getCause() instanceof NullPointerException)) throw e; + } + } } |
From: <svn...@os...> - 2012-06-06 08:54:27
|
Author: jive Date: 2012-06-06 01:54:14 -0700 (Wed, 06 Jun 2012) New Revision: 38799 Modified: trunk/modules/library/jdbc/src/main/java/org/geotools/jdbc/JDBCDataStore.java Log: Improvided NPE getConnection() message as per GEOT-4165 Modified: trunk/modules/library/jdbc/src/main/java/org/geotools/jdbc/JDBCDataStore.java =================================================================== --- trunk/modules/library/jdbc/src/main/java/org/geotools/jdbc/JDBCDataStore.java 2012-06-06 02:56:09 UTC (rev 38798) +++ trunk/modules/library/jdbc/src/main/java/org/geotools/jdbc/JDBCDataStore.java 2012-06-06 08:54:14 UTC (rev 38799) @@ -1646,6 +1646,9 @@ protected final Connection createConnection() { try { LOGGER.fine( "CREATE CONNECTION"); + if( getDataSource() == null ){ + throw new NullPointerException("JDBC DataSource not available after dispose() has been called"); + } Connection cx = getDataSource().getConnection(); // isolation level is not set in the datastore, see // http://jira.codehaus.org/browse/GEOT-2021 |
From: <svn...@os...> - 2012-06-06 02:56:19
|
Author: mbedward Date: 2012-06-05 19:56:09 -0700 (Tue, 05 Jun 2012) New Revision: 38798 Modified: trunk/modules/unsupported/process-raster/src/main/java/org/geotools/process/raster/VectorToRasterProcess.java Log: GEOT-4151: better handling of user-requested bounds in VectorToRasterProcess Modified: trunk/modules/unsupported/process-raster/src/main/java/org/geotools/process/raster/VectorToRasterProcess.java =================================================================== --- trunk/modules/unsupported/process-raster/src/main/java/org/geotools/process/raster/VectorToRasterProcess.java 2012-06-02 08:24:23 UTC (rev 38797) +++ trunk/modules/unsupported/process-raster/src/main/java/org/geotools/process/raster/VectorToRasterProcess.java 2012-06-06 02:56:09 UTC (rev 38798) @@ -46,6 +46,10 @@ import javax.media.jai.RasterFactory; import javax.media.jai.TiledImage; +import com.vividsolutions.jts.geom.Coordinate; +import com.vividsolutions.jts.geom.Geometry; +import com.vividsolutions.jts.geom.GeometryFactory; + import org.geotools.coverage.grid.GridCoordinates2D; import org.geotools.coverage.grid.GridCoverage2D; import org.geotools.coverage.grid.GridCoverageFactory; @@ -57,25 +61,23 @@ import org.geotools.filter.text.ecql.ECQL; import org.geotools.geometry.DirectPosition2D; import org.geotools.geometry.jts.Geometries; +import org.geotools.geometry.jts.JTS; import org.geotools.geometry.jts.ReferencedEnvelope; import org.geotools.process.feature.AbstractFeatureCollectionProcess; import org.geotools.process.feature.AbstractFeatureCollectionProcessFactory; import org.geotools.referencing.CRS; import org.geotools.util.NullProgressListener; import org.geotools.util.SimpleInternationalString; - import org.opengis.feature.simple.SimpleFeature; import org.opengis.feature.type.AttributeDescriptor; import org.opengis.filter.expression.Expression; import org.opengis.geometry.Envelope; +import org.opengis.geometry.MismatchedDimensionException; import org.opengis.referencing.crs.CoordinateReferenceSystem; +import org.opengis.referencing.operation.MathTransform; import org.opengis.referencing.operation.TransformException; import org.opengis.util.ProgressListener; -import com.vividsolutions.jts.geom.Coordinate; -import com.vividsolutions.jts.geom.Geometry; -import com.vividsolutions.jts.geom.GeometryFactory; - /** * A Process to rasterize vector features in an input FeatureCollection. * <p> @@ -115,9 +117,11 @@ private float nodataValue; private ReferencedEnvelope extent; + private Geometry extentGeometry; private GridGeometry2D gridGeom; - private Geometry extentGeometry; + private boolean transformFeatures; + private MathTransform featureToRasterTransform; private int[] coordGridX = new int[COORD_GRID_CHUNK_SIZE]; private int[] coordGridY = new int[COORD_GRID_CHUNK_SIZE]; @@ -439,7 +443,12 @@ minAttValue = maxAttValue = null; - setBounds( features, bounds, gridDim ); + try { + setBounds(features, bounds); + } catch (TransformException ex) { + throw new VectorToRasterException(ex); + } + createImage( gridDim ); gridGeom = new GridGeometry2D( @@ -448,54 +457,48 @@ } /** + * Sets the output coverage bounds and checks whether features need to be + * transformed into the output CRS. * - * @param env + * @param * @throws org.geotools.process.raster.VectorToRasterException */ - private void setBounds( SimpleFeatureCollection features, - Envelope bounds, Dimension gridDim ) throws VectorToRasterException { + private void setBounds( SimpleFeatureCollection features, Envelope bounds) + throws TransformException { ReferencedEnvelope featureBounds = features.getBounds(); - if (bounds != null) { - ReferencedEnvelope inputBounds = new ReferencedEnvelope(bounds); - CoordinateReferenceSystem featuresCRS = featureBounds.getCoordinateReferenceSystem(); - CoordinateReferenceSystem envCRS = bounds.getCoordinateReferenceSystem(); + if (bounds == null) { + extent = featureBounds; + + } else { + extent = new ReferencedEnvelope(bounds); + } + + extentGeometry = (new GeometryFactory()).toGeometry(extent); + + // Compare the CRS of faetures and requested output bounds. If they + // are different (and both non-null) flag that we need to transform + // features to the output CRS prior to rasterizing them. - ReferencedEnvelope trEnv; - if (!CRS.equalsIgnoreMetadata(envCRS, featuresCRS)) { - try { - trEnv = inputBounds.transform(featuresCRS, true); - } catch (Exception tex) { - throw new VectorToRasterException(tex); - } + CoordinateReferenceSystem featuresCRS = featureBounds.getCoordinateReferenceSystem(); + CoordinateReferenceSystem boundsCRS = bounds.getCoordinateReferenceSystem(); - } else { - trEnv = inputBounds; - } - - // If the provided bounds cover the feature bounds, use them - if (trEnv.covers(features.getBounds())) { - extent = trEnv; + transformFeatures = false; + if (featuresCRS != null + && boundsCRS != null + && !CRS.equalsIgnoreMetadata(boundsCRS, featuresCRS)) { + + try { + featureToRasterTransform = CRS.findMathTransform(featuresCRS, boundsCRS, true); - } else { - // If the provided bounds partially overlap the feature bounds - // use the intersection - com.vividsolutions.jts.geom.Envelope common = trEnv.intersection(features.getBounds()); - if (common == null || common.isNull()) { - throw new VectorToRasterException( - "Features do not lie within the requested rasterizing bounds"); - } - extent = new ReferencedEnvelope(common, featuresCRS); + } catch (Exception ex) { + throw new TransformException( + "Unable to transform features into output coordinate reference system", ex); } - - } else { - // No bounds provided - use feature bounds - extent = featureBounds; + + transformFeatures = true; } - - GeometryFactory gf = new GeometryFactory(); - extentGeometry = gf.toGeometry(extent); } /** @@ -603,8 +606,21 @@ image = destImage; } - private void drawGeometry(Geometries geomType, Geometry geometry) { - + private void drawGeometry(Geometries geomType, Geometry geometry) throws TransformException { + Geometry workingGeometry; + if (transformFeatures) { + try { + workingGeometry = JTS.transform(geometry, featureToRasterTransform); + } catch (TransformException ex) { + throw ex; + } catch (MismatchedDimensionException ex) { + throw new RuntimeException(ex); + } + + } else { + workingGeometry = geometry; + } + Coordinate[] coords = geometry.getCoordinates(); // enlarge if needed @@ -616,16 +632,11 @@ // Go through coordinate array in order received DirectPosition2D worldPos = new DirectPosition2D(); - try { - for (int n = 0; n < coords.length; n++) { - worldPos.setLocation(coords[n].x, coords[n].y); - GridCoordinates2D gridPos = gridGeom.worldToGrid(worldPos); - coordGridX[n] = gridPos.x; - coordGridY[n] = gridPos.y; - } - - } catch (TransformException ex) { - throw new RuntimeException(ex); + for (int n = 0; n < coords.length; n++) { + worldPos.setLocation(coords[n].x, coords[n].y); + GridCoordinates2D gridPos = gridGeom.worldToGrid(worldPos); + coordGridX[n] = gridPos.x; + coordGridY[n] = gridPos.y; } switch (geomType) { |
From: <svn...@os...> - 2012-06-02 08:24:29
|
Author: aaime Date: 2012-06-02 01:24:23 -0700 (Sat, 02 Jun 2012) New Revision: 38797 Modified: trunk/modules/library/xml/src/main/java/org/geotools/xml/XMLSAXHandler.java Log: Prevent NPE when we have an error but the locator is null (happens when we have invalid charset declaration in the xml Modified: trunk/modules/library/xml/src/main/java/org/geotools/xml/XMLSAXHandler.java =================================================================== --- trunk/modules/library/xml/src/main/java/org/geotools/xml/XMLSAXHandler.java 2012-06-02 08:23:48 UTC (rev 38796) +++ trunk/modules/library/xml/src/main/java/org/geotools/xml/XMLSAXHandler.java 2012-06-02 08:24:23 UTC (rev 38797) @@ -486,8 +486,10 @@ public void fatalError(SAXParseException exception) throws SAXException { logger.severe("FATAL " + exception.getMessage()); - logger.severe("col " + locator.getColumnNumber() + ", line " - + locator.getLineNumber()); + if(locator != null) { + logger.severe("col " + locator.getColumnNumber() + ", line " + + locator.getLineNumber()); + } throw exception; } |
From: <svn...@os...> - 2012-06-02 08:23:55
|
Author: aaime Date: 2012-06-02 01:23:48 -0700 (Sat, 02 Jun 2012) New Revision: 38796 Modified: branches/2.7.x/modules/library/xml/src/main/java/org/geotools/xml/XMLSAXHandler.java Log: Prevent NPE when we have an error but the locator is null (happens when we have invalid charset declaration in the xml Modified: branches/2.7.x/modules/library/xml/src/main/java/org/geotools/xml/XMLSAXHandler.java =================================================================== --- branches/2.7.x/modules/library/xml/src/main/java/org/geotools/xml/XMLSAXHandler.java 2012-06-01 16:39:50 UTC (rev 38795) +++ branches/2.7.x/modules/library/xml/src/main/java/org/geotools/xml/XMLSAXHandler.java 2012-06-02 08:23:48 UTC (rev 38796) @@ -485,8 +485,10 @@ public void fatalError(SAXParseException exception) throws SAXException { logger.severe("FATAL " + exception.getMessage()); - logger.severe("col " + locator.getColumnNumber() + ", line " - + locator.getLineNumber()); + if(locator != null) { + logger.severe("col " + locator.getColumnNumber() + ", line " + + locator.getLineNumber()); + } throw exception; } |