Skip to content

Commit

Permalink
Initial support for product searches
Browse files Browse the repository at this point in the history
  • Loading branch information
aaime committed Mar 14, 2017
1 parent 1d0be30 commit fd74784
Show file tree
Hide file tree
Showing 12 changed files with 3,885 additions and 51 deletions.
@@ -0,0 +1,49 @@
/* (c) 2017 Open Source Geospatial Foundation - all rights reserved
* This code is licensed under the GPL 2.0 license, available at the root
* application directory.
*/
package org.geoserver.opensearch.eo;

import org.geotools.feature.NameImpl;
import org.opengis.feature.Feature;
import org.opengis.feature.Property;

/**
* Utility class to get complex feature attribute values (actual values, not property wrappers).
* Can be used to simplify access to values when they are single valued and do not involve
*
*
* @author Andrea Aime - GeoSolutions
*/
public class ComplexFeatureAccessor {

/**
* Returns a single attribute value assuming the attribute is in the same namespace as the
* feature
*
* @param feature
* @param attribute
* @return
*/
public static Object value(Feature feature, String attribute) {
String prefix = feature.getType().getName().getNamespaceURI();
return value(feature, prefix, attribute);
}

/**
* Returns a single attribute value looking it up by qualified name
* @param feature
* @param namespace
* @param attribute
* @return
*/
public static Object value(Feature feature, String namespace, String attribute) {
Property property = feature.getProperty(new NameImpl(namespace, attribute));
if (property == null) {
return null;
} else {
Object value = property.getValue();
return value;
}
}
}
Expand Up @@ -4,13 +4,16 @@
*/ */
package org.geoserver.opensearch.eo; package org.geoserver.opensearch.eo;


import static org.geoserver.opensearch.eo.ComplexFeatureAccessor.*;

import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.logging.Logger; import java.util.logging.Logger;


import org.geoserver.catalog.DataStoreInfo; import org.geoserver.catalog.DataStoreInfo;
import org.geoserver.catalog.Predicates;
import org.geoserver.config.GeoServer; import org.geoserver.config.GeoServer;
import org.geoserver.opensearch.eo.store.OpenSearchAccess; import org.geoserver.opensearch.eo.store.OpenSearchAccess;
import org.geoserver.opensearch.eo.store.OpenSearchAccess.ProductClass; import org.geoserver.opensearch.eo.store.OpenSearchAccess.ProductClass;
Expand All @@ -32,6 +35,7 @@
import org.opengis.feature.type.PropertyDescriptor; import org.opengis.feature.type.PropertyDescriptor;
import org.opengis.filter.FilterFactory2; import org.opengis.filter.FilterFactory2;
import org.opengis.filter.PropertyIsEqualTo; import org.opengis.filter.PropertyIsEqualTo;
import org.opengis.filter.expression.PropertyName;


import com.vividsolutions.jts.geom.Geometry; import com.vividsolutions.jts.geom.Geometry;


Expand Down Expand Up @@ -74,13 +78,7 @@ public List<Parameter<?>> getProductSearchParameters(final String parentId) thro
searchParameters.addAll(OpenSearchParameters.getGeoTimeOpensearch()); searchParameters.addAll(OpenSearchParameters.getGeoTimeOpensearch());


// product search for a given collection, figure out what parameters apply to it // product search for a given collection, figure out what parameters apply to it
OpenSearchAccess access = getOpenSearchAccess(); Feature match = getCollectionByParentIdentifier(parentId);
final FeatureSource<FeatureType, Feature> collectionSource = access.getCollectionSource();
Feature match = getCollectionByParentId(parentId, collectionSource);
if (match == null) {
throw new OWS20Exception("Unknown parentId '" + parentId + "'",
OWSExceptionCode.InvalidParameterValue);
}
Property sensorTypeProperty = match Property sensorTypeProperty = match
.getProperty(new NameImpl(OpenSearchAccess.EO_NAMESPACE, "sensorType")); .getProperty(new NameImpl(OpenSearchAccess.EO_NAMESPACE, "sensorType"));
if (sensorTypeProperty == null || !(sensorTypeProperty.getValue() instanceof String)) { if (sensorTypeProperty == null || !(sensorTypeProperty.getValue() instanceof String)) {
Expand All @@ -96,15 +94,42 @@ public List<Parameter<?>> getProductSearchParameters(final String parentId) thro
+ ", will only return generic product properties"); + ", will only return generic product properties");
} }


OpenSearchAccess access = getOpenSearchAccess();
FeatureType productSchema = access.getProductSource().getSchema(); FeatureType productSchema = access.getProductSource().getSchema();
searchParameters.addAll(getSearchParametersByClass(ProductClass.EO_GENERIC, productSchema)); searchParameters.addAll(getSearchParametersByClass(ProductClass.EO_GENERIC, productSchema));
if(collectionClass != null) { if (collectionClass != null) {
searchParameters.addAll(getSearchParametersByClass(collectionClass, productSchema)); searchParameters.addAll(getSearchParametersByClass(collectionClass, productSchema));
} }


return searchParameters; return searchParameters;
} }


/**
* Returns the complex feature representing a collection by parentId
* @param parentId
* @return
* @throws IOException
*/
private Feature getCollectionByParentIdentifier(final String parentId) throws IOException {
OpenSearchAccess access = getOpenSearchAccess();
final FeatureSource<FeatureType, Feature> collectionSource = access.getCollectionSource();

// build the query
final NameImpl identifier = new NameImpl(OpenSearchAccess.EO_NAMESPACE, "identifier");
final PropertyIsEqualTo filter = FF.equal(FF.property(identifier), FF.literal(parentId),
true);
Query query = new Query(collectionSource.getName().getLocalPart(), filter);
FeatureCollection<FeatureType, Feature> features = collectionSource.getFeatures(query);

// get the expected maching feature
Feature match = DataUtilities.first(features);
if (match == null) {
throw new OWS20Exception("Unknown parentId '" + parentId + "'",
OWSExceptionCode.InvalidParameterValue);
}
return match;
}

private List<Parameter<?>> getSearchParametersByClass(ProductClass pc, private List<Parameter<?>> getSearchParametersByClass(ProductClass pc,
FeatureType productSchema) { FeatureType productSchema) {
List<Parameter<?>> result = new ArrayList<>(); List<Parameter<?>> result = new ArrayList<>();
Expand All @@ -122,17 +147,6 @@ private List<Parameter<?>> getSearchParametersByClass(ProductClass pc,
return result; return result;
} }


private Feature getCollectionByParentId(final String parentId,
final FeatureSource<FeatureType, Feature> collectionSource) throws IOException {
final NameImpl identifier = new NameImpl(OpenSearchAccess.EO_NAMESPACE, "identifier");
final PropertyIsEqualTo filter = FF.equal(FF.property(identifier), FF.literal(parentId),
true);
Query query = new Query(collectionSource.getName().getLocalPart(), filter);
FeatureCollection<FeatureType, Feature> features = collectionSource.getFeatures(query);
Feature match = DataUtilities.first(features);
return match;
}

public List<Parameter<?>> getCollectionSearchParameters() throws IOException { public List<Parameter<?>> getCollectionSearchParameters() throws IOException {
OSEOInfo service = getService(); OSEOInfo service = getService();
List<Parameter<?>> searchParameters = new ArrayList<>(); List<Parameter<?>> searchParameters = new ArrayList<>();
Expand Down Expand Up @@ -161,23 +175,40 @@ private Collection<? extends Parameter<?>> getCollectionEoSearchParameters()


@Override @Override
public SearchResults search(SearchRequest request) throws IOException { public SearchResults search(SearchRequest request) throws IOException {
if (request.getParentId() != null) { // grab the right feature source for the request
throw new OWS20Exception("Product search not implemented yet");
}

// feature request
final OpenSearchAccess access = getOpenSearchAccess(); final OpenSearchAccess access = getOpenSearchAccess();
final FeatureSource<FeatureType, Feature> collectionSource = access.getCollectionSource(); final FeatureSource<FeatureType, Feature> featureSource;

Query resultsQuery = request.getQuery();
final String parentId = request.getParentId();
if (parentId == null) {
featureSource = access.getCollectionSource();
} else {
featureSource = access.getProductSource();

// need to determine if the collection is primary or virtual
Feature collection = getCollectionByParentIdentifier(parentId);
if(Boolean.FALSE.equals(value(collection, "primary"))) {
// TODO: parse and integrate virtual collection filter
throw new OWS20Exception("Virtual collection support not implemented yet");
} else {
// adding parent id filter for primary collections
final PropertyName parentIdProperty = FF
.property(new NameImpl(OpenSearchAccess.EO_NAMESPACE, "parentIdentifier"));
PropertyIsEqualTo parentIdFilter = FF.equal(parentIdProperty, FF.literal(parentId),
true);
resultsQuery = new Query(resultsQuery);
resultsQuery.setFilter(Predicates.and(resultsQuery.getFilter(), parentIdFilter));
}
}

// count // count
final Query resultsQuery = request.getQuery();
Query countQuery = new Query(resultsQuery); Query countQuery = new Query(resultsQuery);
countQuery.setMaxFeatures(Query.DEFAULT_MAX); countQuery.setMaxFeatures(Query.DEFAULT_MAX);
countQuery.setStartIndex(null); countQuery.setStartIndex(null);
int totalResults = collectionSource.getCount(countQuery); int totalResults = featureSource.getCount(countQuery);


// get actual features // get actual features
FeatureCollection features = collectionSource.getFeatures(resultsQuery); FeatureCollection<FeatureType, Feature> features = featureSource.getFeatures(resultsQuery);
SearchResults results = new SearchResults(request, features, totalResults); SearchResults results = new SearchResults(request, features, totalResults);


return results; return results;
Expand Down
Expand Up @@ -48,6 +48,8 @@ public class SearchRequestKvpReader extends KvpRequestReader {
static final FilterFactory2 FF = CommonFactoryFinder.getFilterFactory2(); static final FilterFactory2 FF = CommonFactoryFinder.getFilterFactory2();


public static final String COUNT_KEY = "count"; public static final String COUNT_KEY = "count";

public static final String PARENT_ID_KEY = "parentId";


private Set<String> NOT_FILTERS = new HashSet<>(Arrays.asList(START_INDEX.key, COUNT_KEY)); private Set<String> NOT_FILTERS = new HashSet<>(Arrays.asList(START_INDEX.key, COUNT_KEY));


Expand Down
Expand Up @@ -169,9 +169,12 @@ private void encodeEntries(FeatureCollection results, SearchRequest request) {
BiConsumer<Feature, SearchRequest> entryEncoder; BiConsumer<Feature, SearchRequest> entryEncoder;
if (JDBCOpenSearchAccess.COLLECTION.equals(schemaName)) { if (JDBCOpenSearchAccess.COLLECTION.equals(schemaName)) {
entryEncoder = this::encodeCollectionEntry; entryEncoder = this::encodeCollectionEntry;
} else if (JDBCOpenSearchAccess.PRODUCT.equals(schemaName)) {
entryEncoder = this::encodeProductEntry;
} else { } else {
throw new IllegalArgumentException("Unrecognized feature type " + schemaName); throw new IllegalArgumentException("Unrecognized feature type " + schemaName);
} }

try (FeatureIterator<Feature> fi = results.features()) { try (FeatureIterator<Feature> fi = results.features()) {
while (fi.hasNext()) { while (fi.hasNext()) {
Feature feature = fi.next(); Feature feature = fi.next();
Expand All @@ -181,9 +184,21 @@ private void encodeEntries(FeatureCollection results, SearchRequest request) {
} }


private void encodeCollectionEntry(Feature feature, SearchRequest request) { private void encodeCollectionEntry(Feature feature, SearchRequest request) {
final String buildCollectionIdentifierLink = buildCollectionIdentifierLink(value(feature, EO_NAMESPACE, "identifier"), final String identifierLink = buildCollectionIdentifierLink(
request); value(feature, EO_NAMESPACE, "identifier"), request);
element("id", buildCollectionIdentifierLink); encodeGenericEntryContents(feature, identifierLink);

}

private void encodeProductEntry(Feature feature, SearchRequest request) {
final String buildCollectionIdentifierLink = buildProductIdentifierLink(
value(feature, EO_NAMESPACE, "identifier"), request);
encodeGenericEntryContents(feature, buildCollectionIdentifierLink);

}

private void encodeGenericEntryContents(Feature feature, final String identifierLink) {
element("id", identifierLink);
element("title", (String) value(feature, "name")); element("title", (String) value(feature, "name"));
// TODO: need an actual update column // TODO: need an actual update column
Date updated = (Date) value(feature, "timeStart"); Date updated = (Date) value(feature, "timeStart");
Expand All @@ -200,9 +215,8 @@ private void encodeCollectionEntry(Feature feature, SearchRequest request) {
element("summary", () -> cdata(htmlDescription), attributes("type", "html")); element("summary", () -> cdata(htmlDescription), attributes("type", "html"));
} }
// self link // self link
element("link", NO_CONTENTS, element("link", NO_CONTENTS, attributes("rel", "alternate", "href", identifierLink,
attributes("rel", "alternate", "href", buildCollectionIdentifierLink, "type", AtomSearchResponse.MIME)); "type", AtomSearchResponse.MIME));

} }


private void encodeGmlRssGeometry(Geometry g) { private void encodeGmlRssGeometry(Geometry g) {
Expand Down Expand Up @@ -236,6 +250,16 @@ private String buildCollectionIdentifierLink(Object identifier, SearchRequest re
return href; return href;
} }


private String buildProductIdentifierLink(Object identifier, SearchRequest request) {
String baseURL = request.getBaseUrl();
Map<String, String> kvp = new LinkedHashMap<String, String>();
kvp.put("parentId", request.getParentId());
kvp.put("uid", String.valueOf(identifier));
kvp.put("httpAccept", AtomSearchResponse.MIME);
String href = ResponseUtils.buildURL(baseURL, "oseo/search", kvp, URLType.SERVICE);
return href;
}

private Object value(Feature feature, String attribute) { private Object value(Feature feature, String attribute) {
String prefix = feature.getType().getName().getNamespaceURI(); String prefix = feature.getType().getName().getNamespaceURI();
return value(feature, prefix, attribute); return value(feature, prefix, attribute);
Expand All @@ -247,17 +271,21 @@ private Object value(Feature feature, String prefix, String attribute) {
return null; return null;
} else { } else {
Object value = property.getValue(); Object value = property.getValue();
if(value instanceof Geometry) { if (value instanceof Geometry) {
// cheap reprojection support since there is no reprojecting collection // cheap reprojection support since there is no reprojecting collection
// wrapper for complex features // wrapper for complex features
CoordinateReferenceSystem nativeCRS = ((GeometryDescriptor) property.getDescriptor()).getCoordinateReferenceSystem(); CoordinateReferenceSystem nativeCRS = ((GeometryDescriptor) property
if(nativeCRS != null && !CRS.equalsIgnoreMetadata(nativeCRS, OpenSearchParameters.OUTPUT_CRS)) { .getDescriptor()).getCoordinateReferenceSystem();
if (nativeCRS != null && !CRS.equalsIgnoreMetadata(nativeCRS,
OpenSearchParameters.OUTPUT_CRS)) {
Geometry g = (Geometry) value; Geometry g = (Geometry) value;
try { try {
return JTS.transform(g, CRS.findMathTransform(nativeCRS, OpenSearchParameters.OUTPUT_CRS)); return JTS.transform(g, CRS.findMathTransform(nativeCRS,
OpenSearchParameters.OUTPUT_CRS));
} catch (MismatchedDimensionException | TransformException } catch (MismatchedDimensionException | TransformException
| FactoryException e) { | FactoryException e) {
throw new OWS20Exception("Failed to reproject geometry to EPSG:4326 lat/lon", e); throw new OWS20Exception(
"Failed to reproject geometry to EPSG:4326 lat/lon", e);
} }
} }
} }
Expand All @@ -266,6 +294,10 @@ private Object value(Feature feature, String prefix, String attribute) {
} }


private int getLastPageStart(int total, int itemsPerPage) { private int getLastPageStart(int total, int itemsPerPage) {
// all in one page?
if(total <= itemsPerPage) {
return 1;
}
// check how many items in the last page, is the last page partial or full? // check how many items in the last page, is the last page partial or full?
int lastPageItems = total % itemsPerPage; int lastPageItems = total % itemsPerPage;
if (lastPageItems == 0) { if (lastPageItems == 0) {
Expand Down
Expand Up @@ -43,7 +43,7 @@ public class JDBCOpenSearchAccess implements OpenSearchAccess {


public static final String COLLECTION = "collection"; public static final String COLLECTION = "collection";


static final String PRODUCT = "product"; public static final String PRODUCT = "product";


static final String EO_PREFIX = "eo"; static final String EO_PREFIX = "eo";


Expand Down
Expand Up @@ -60,6 +60,7 @@ public static void setupStore() throws IOException, SQLException {
h2 = (JDBCDataStore) DataStoreFinder.getDataStore(params); h2 = (JDBCDataStore) DataStoreFinder.getDataStore(params);
JDBCOpenSearchAccessTest.createTables(h2); JDBCOpenSearchAccessTest.createTables(h2);
JDBCOpenSearchAccessTest.populateCollections(h2); JDBCOpenSearchAccessTest.populateCollections(h2);
JDBCOpenSearchAccessTest.populateProducts(h2);


Name name = new NameImpl("test", "jdbcStore"); Name name = new NameImpl("test", "jdbcStore");
SerializableDefaultRepository repository = new SerializableDefaultRepository(); SerializableDefaultRepository repository = new SerializableDefaultRepository();
Expand Down Expand Up @@ -94,7 +95,7 @@ private static List<String> loadScriptCommands(String scriptLocation) throws IOE
} else { } else {
buffer = buffer + "\n" + line; buffer = buffer + "\n" + line;
} }
if (line.contains(";")) { if (line.trim().endsWith(";")) {
statements.add(buffer); statements.add(buffer);
buffer = null; buffer = null;
} }
Expand Down Expand Up @@ -132,17 +133,29 @@ static void createTables(JDBCDataStore h2) throws SQLException, IOException {
} }


/** /**
* Takes the postgis.sql creation script, adapts it and runs it on H2 * Adds the collection data into the H2 database
*/ */
static void populateCollections(JDBCDataStore h2) throws SQLException, IOException { static void populateCollections(JDBCDataStore h2) throws SQLException, IOException {
List<String> statements = loadScriptCommands("/collection_h2_data.sql"); runScript("/collection_h2_data.sql", h2);
}

/**
* Adds the product data into the H2 database
*/
static void populateProducts(JDBCDataStore h2) throws SQLException, IOException {
runScript("/product_h2_data.sql", h2);
}

private static void runScript(String script, JDBCDataStore h2) throws IOException, SQLException {
List<String> statements = loadScriptCommands(script);
try (Connection conn = h2.getConnection(Transaction.AUTO_COMMIT); try (Connection conn = h2.getConnection(Transaction.AUTO_COMMIT);
Statement st = conn.createStatement();) { Statement st = conn.createStatement();) {
for (String statement : statements) { for (String statement : statements) {
st.execute(statement); st.execute(statement);
} }
} }
} }



@Test @Test
public void testCollectionFeatureType() throws Exception { public void testCollectionFeatureType() throws Exception {
Expand Down
Expand Up @@ -96,6 +96,7 @@ public static void setupBasicOpenSearch(SystemTestData testData, Catalog cat, Ge
JDBCDataStore h2 = (JDBCDataStore) jdbcDs.getDataStore(null); JDBCDataStore h2 = (JDBCDataStore) jdbcDs.getDataStore(null);
JDBCOpenSearchAccessTest.createTables(h2); JDBCOpenSearchAccessTest.createTables(h2);
JDBCOpenSearchAccessTest.populateCollections(h2); JDBCOpenSearchAccessTest.populateCollections(h2);
JDBCOpenSearchAccessTest.populateProducts(h2);


// create the OpenSeach wrapper store // create the OpenSeach wrapper store
DataStoreInfo osDs = cat.getFactory().createDataStore(); DataStoreInfo osDs = cat.getFactory().createDataStore();
Expand Down

0 comments on commit fd74784

Please sign in to comment.