diff --git a/src/main/java/org/heigit/bigspatialdata/ohsome/ohsomeApi/controller/executor/ElementsRequestExecutor.java b/src/main/java/org/heigit/bigspatialdata/ohsome/ohsomeApi/controller/executor/ElementsRequestExecutor.java index 99b0489e..c47accda 100644 --- a/src/main/java/org/heigit/bigspatialdata/ohsome/ohsomeApi/controller/executor/ElementsRequestExecutor.java +++ b/src/main/java/org/heigit/bigspatialdata/ohsome/ohsomeApi/controller/executor/ElementsRequestExecutor.java @@ -3,6 +3,7 @@ import java.text.DecimalFormat; import java.util.ArrayList; import java.util.Arrays; +import java.util.EnumSet; import java.util.LinkedList; import java.util.List; import java.util.Map.Entry; @@ -11,6 +12,7 @@ import org.apache.commons.lang3.tuple.ImmutablePair; import org.apache.commons.lang3.tuple.Pair; import org.heigit.bigspatialdata.ohsome.ohsomeApi.Application; +import org.heigit.bigspatialdata.ohsome.ohsomeApi.controller.executor.ExecutionUtils.MatchType; import org.heigit.bigspatialdata.ohsome.ohsomeApi.exception.BadRequestException; import org.heigit.bigspatialdata.ohsome.ohsomeApi.inputProcessing.GeometryBuilder; import org.heigit.bigspatialdata.ohsome.ohsomeApi.inputProcessing.InputProcessor; @@ -38,6 +40,7 @@ import org.heigit.bigspatialdata.oshdb.api.mapreducer.MapAggregatorByTimestampAndIndex; import org.heigit.bigspatialdata.oshdb.api.mapreducer.MapReducer; import org.heigit.bigspatialdata.oshdb.api.object.OSMEntitySnapshot; +import org.heigit.bigspatialdata.oshdb.osm.OSMEntity; import org.heigit.bigspatialdata.oshdb.osm.OSMType; import org.heigit.bigspatialdata.oshdb.util.OSHDBTag; import org.heigit.bigspatialdata.oshdb.util.OSHDBTimestamp; @@ -554,122 +557,6 @@ public static GroupByResponse executeCountPerimeterAreaGroupByType( return response; } - /** - * Performs a count|length|perimeter|area-ratio calculation. - *

- * The other parameters are described in the - * {@link org.heigit.bigspatialdata.ohsome.ohsomeApi.controller.dataAggregation.CountController#getCountRatio(String, String, String, String[], String[], String[], String[], String[], String, String[], String[], String[]) - * getCountRatio} method. - * - * @param requestResource Enum defining the request type (COUNT, LENGTH, PERIMETER, - * AREA). - * @param rPs RequestParameters object, which holds those parameters that are used in - * every request. - * @return {@link org.heigit.bigspatialdata.ohsome.ohsomeApi.output.dataAggregationResponse.DefaultAggregationResponse - * DefaultAggregationResponse} - */ - public static RatioResponse executeCountLengthPerimeterAreaRatio(RequestResource requestResource, - RequestParameters rPs, String[] types2, String[] keys2, String[] values2) - throws UnsupportedOperationException, Exception { - - long startTime = System.currentTimeMillis(); - ExecutionUtils exeUtils = new ExecutionUtils(); - SortedMap result1 = null; - SortedMap result2 = null; - MapReducer mapRed1 = null; - MapReducer mapRed2 = null; - InputProcessor iP = new InputProcessor(); - String description = null; - String requestURL = null; - DecimalFormat ratioDf = exeUtils.defineDecimalFormat("#.######"); - DecimalFormat df = exeUtils.defineDecimalFormat("#.##"); - RequestParameters rPs2 = new RequestParameters(rPs.isPost(), rPs.isSnapshot(), rPs.isDensity(), - rPs.getBboxes(), rPs.getBcircles(), rPs.getBpolys(), types2, keys2, values2, - rPs.getUserids(), rPs.getTime(), rPs.getShowMetadata()); - if (!rPs.isPost()) - requestURL = RequestInterceptor.requestUrl; - mapRed1 = iP.processParameters(mapRed1, rPs); - mapRed2 = iP.processParameters(mapRed2, rPs2); - switch (requestResource) { - case COUNT: - result1 = mapRed1.aggregateByTimestamp().count(); - result2 = mapRed2.aggregateByTimestamp().count(); - break; - case AREA: - result1 = mapRed1.aggregateByTimestamp() - .sum((SerializableFunction) snapshot -> { - return Geo.areaOf(snapshot.getGeometry()); - }); - result2 = mapRed2.aggregateByTimestamp() - .sum((SerializableFunction) snapshot -> { - return Geo.areaOf(snapshot.getGeometry()); - }); - break; - case LENGTH: - result1 = mapRed1.aggregateByTimestamp() - .sum((SerializableFunction) snapshot -> { - return Geo.lengthOf(snapshot.getGeometry()); - }); - result2 = mapRed2.aggregateByTimestamp() - .sum((SerializableFunction) snapshot -> { - return Geo.lengthOf(snapshot.getGeometry()); - }); - break; - case PERIMETER: - result1 = mapRed1.aggregateByTimestamp() - .sum((SerializableFunction) snapshot -> { - if (snapshot.getGeometry() instanceof Polygonal) - return Geo.lengthOf(snapshot.getGeometry().getBoundary()); - else - return 0.0; - }); - result2 = mapRed2.aggregateByTimestamp() - .sum((SerializableFunction) snapshot -> { - if (snapshot.getGeometry() instanceof Polygonal) - return Geo.lengthOf(snapshot.getGeometry().getBoundary()); - else - return 0.0; - }); - break; - } - ElementsResult[] resultSet1 = new ElementsResult[result1.size()]; - int count = 0; - for (Entry entry : result1.entrySet()) { - resultSet1[count] = - new ElementsResult(TimestampFormatter.getInstance().isoDateTime(entry.getKey()), - entry.getValue().doubleValue()); - count++; - } - RatioResult[] resultSet = new RatioResult[result1.size()]; - count = 0; - for (Entry entry : result2.entrySet()) { - String date = resultSet1[count].getTimestamp(); - double ratio = (entry.getValue().doubleValue() / resultSet1[count].getValue()); - // in case ratio has the value "NaN", "Infinity", etc. - try { - ratio = Double.parseDouble(ratioDf.format(ratio)); - } catch (Exception e) { - // do nothing --> just return ratio without rounding (trimming) - } - resultSet[count] = - new RatioResult(date, Double.parseDouble(df.format(resultSet1[count].getValue())), - Double.parseDouble(df.format(entry.getValue().doubleValue())), ratio); - count++; - } - description = "Total " + requestResource.getLabel() + " of items in " - + requestResource.getUnit() - + " satisfying types2, keys2, values2 parameters (= value2 output) " - + "within items selected by types, keys, values parameters (= value output) and ratio of value2:value."; - Metadata metadata = null; - if (iP.getShowMetadata()) { - long duration = System.currentTimeMillis() - startTime; - metadata = new Metadata(duration, description, requestURL); - } - RatioResponse response = - new RatioResponse(new Attribution(url, text), Application.apiVersion, metadata, resultSet); - return response; - } - /** * Performs a count-ratio calculation grouped by the boundary. *

@@ -1137,4 +1024,158 @@ public static ShareGroupByBoundaryResponse executeCountLengthPerimeterAreaShareG return response; } + /** + * Performs a count|length|perimeter|area-share|ratio calculation. + *

+ * The other parameters are described in the + * {@link org.heigit.bigspatialdata.ohsome.ohsomeApi.controller.dataAggregation.CountController#getCountRatio(String, String, String, String[], String[], String[], String[], String[], String, String[], String[], String[]) + * getCountRatio} method. + * + * @param requestResource Enum defining the request type (COUNT, LENGTH, PERIMETER, + * AREA). + * @param rPs RequestParameters object, which holds those parameters that are used in + * every request. + * @return {@link org.heigit.bigspatialdata.ohsome.ohsomeApi.output.dataAggregationResponse.DefaultAggregationResponse + * DefaultAggregationResponse} + */ + public static RatioResponse executeCountLengthPerimeterAreaRatio( + RequestResource requestResource, RequestParameters rPs, String[] types2, String[] keys2, + String[] values2) throws UnsupportedOperationException, Exception { + + long startTime = System.currentTimeMillis(); + ExecutionUtils exeUtils = new ExecutionUtils(); + values2 = exeUtils.ratioParamEvaluation(keys2, values2); + SortedMap, ? extends Number> result = null; + MapAggregatorByTimestampAndIndex preResult; + MapReducer mapRed = null; + InputProcessor iP = new InputProcessor(); + String description = null; + String requestURL = null; + DecimalFormat df = exeUtils.defineDecimalFormat("#.##"); + DecimalFormat ratioDf = exeUtils.defineDecimalFormat("#.######"); + TagTranslator tt = DbConnData.tagTranslator; + rPs = iP.fillWithEmptyIfNull(rPs); + iP.processParameters(mapRed, rPs); + Integer[] keysInt1 = new Integer[rPs.getKeys().length]; + Integer[] valuesInt1 = new Integer[rPs.getValues().length]; + Integer[] keysInt2 = new Integer[keys2.length]; + Integer[] valuesInt2 = new Integer[values2.length]; + if (!rPs.isPost()) + requestURL = RequestInterceptor.requestUrl; + for (int i = 0; i < rPs.getKeys().length; i++) { + keysInt1[i] = tt.getOSHDBTagKeyOf(rPs.getKeys()[i]).toInt(); + if (rPs.getValues() != null && i < rPs.getValues().length) + valuesInt1[i] = tt.getOSHDBTagOf(rPs.getKeys()[i], rPs.getValues()[i]).getValue(); + } + for (int i = 0; i < keys2.length; i++) { + keysInt2[i] = tt.getOSHDBTagKeyOf(keys2[i]).toInt(); + if (values2 != null && i < values2.length) + valuesInt2[i] = tt.getOSHDBTagOf(keys2[i], values2[i]).getValue(); + } + EnumSet osmTypes1 = iP.getOsmTypes(); + EnumSet osmTypes2 = iP.checkTypes(types2); + EnumSet osmTypes = osmTypes1.clone(); + osmTypes.addAll(osmTypes2); + String[] osmTypesString = + osmTypes.stream().map(OSMType::toString).map(String::toLowerCase).toArray(String[]::new); + mapRed = iP.processParameters(mapRed, + new RequestParameters(rPs.isPost(), rPs.isSnapshot(), rPs.isDensity(), rPs.getBboxes(), + rPs.getBcircles(), rPs.getBpolys(), osmTypesString, new String[] {}, new String[] {}, + rPs.getUserids(), rPs.getTime(), rPs.getShowMetadata())); + mapRed = mapRed.where(entity -> { + boolean matches1 = exeUtils.entityMatches(entity, osmTypes1, keysInt1, valuesInt1); + boolean matches2 = exeUtils.entityMatches(entity, osmTypes2, keysInt2, valuesInt2); + return matches1 || matches2; + }); + preResult = mapRed.aggregateByTimestamp().aggregateBy(f -> { + OSMEntity entity = f.getEntity(); + boolean matches1 = exeUtils.entityMatches(entity, osmTypes1, keysInt1, valuesInt1); + boolean matches2 = exeUtils.entityMatches(entity, osmTypes2, keysInt2, valuesInt2); + if (matches1 && matches2) + return MatchType.MATCHESBOTH; + else if (matches1) + return MatchType.MATCHES1; + else if (matches2) + return MatchType.MATCHES2; + else + assert false : "MatchType matches none."; + // this should never be reached + return null; + }).zerofillIndices( + Arrays.asList(MatchType.MATCHESBOTH, MatchType.MATCHES1, MatchType.MATCHES2)); + switch (requestResource) { + case COUNT: + result = preResult.count(); + break; + case LENGTH: + result = preResult.sum((SerializableFunction) snapshot -> { + return Geo.lengthOf(snapshot.getGeometry()); + }); + break; + case PERIMETER: + result = preResult.sum((SerializableFunction) snapshot -> { + if (snapshot.getGeometry() instanceof Polygonal) + return Geo.lengthOf(snapshot.getGeometry().getBoundary()); + else + return 0.0; + }); + break; + case AREA: + result = preResult.sum((SerializableFunction) snapshot -> { + return Geo.areaOf(snapshot.getGeometry()); + }); + break; + } + int resultSize = result.size(); + Double[] value1 = new Double[resultSize / 3]; + Double[] value2 = new Double[resultSize / 3]; + String[] timeArray = new String[resultSize / 3]; + int value1Count = 0; + int value2Count = 0; + int matchesBothCount = 0; + // time and value extraction + for (Entry, ? extends Number> entry : result.entrySet()) { + if (entry.getKey().getOtherIndex() == MatchType.MATCHES2) { + timeArray[value2Count] = + TimestampFormatter.getInstance().isoDateTime(entry.getKey().getTimeIndex()); + value2[value2Count] = Double.parseDouble(df.format(entry.getValue().doubleValue())); + value2Count++; + } + if (entry.getKey().getOtherIndex() == MatchType.MATCHES1) { + value1[value1Count] = Double.parseDouble(df.format(entry.getValue().doubleValue())); + value1Count++; + } + if (entry.getKey().getOtherIndex() == MatchType.MATCHESBOTH) { + value1[matchesBothCount] = value1[matchesBothCount] + + Double.parseDouble(df.format(entry.getValue().doubleValue())); + value2[matchesBothCount] = value2[matchesBothCount] + + Double.parseDouble(df.format(entry.getValue().doubleValue())); + matchesBothCount++; + } + } + RatioResult[] resultSet = new RatioResult[timeArray.length]; + for (int i = 0; i < timeArray.length; i++) { + double ratio = value2[i] / value1[i]; + // in case ratio has the values "NaN", "Infinity", etc. + try { + ratio = Double.parseDouble(ratioDf.format(ratio)); + } catch (Exception e) { + // do nothing --> just return ratio without rounding (trimming) + } + resultSet[i] = new RatioResult(timeArray[i], value1[i], value2[i], ratio); + } + description = "Total " + requestResource.getLabel() + " of items in " + + requestResource.getUnit() + + " satisfying types2, keys2, values2 parameters (= value2 output) " + + "within items selected by types, keys, values parameters (= value output) and ratio of value2:value."; + Metadata metadata = null; + if (iP.getShowMetadata()) { + long duration = System.currentTimeMillis() - startTime; + metadata = new Metadata(duration, description, requestURL); + } + RatioResponse response = + new RatioResponse(new Attribution(url, text), Application.apiVersion, metadata, resultSet); + return response; + } + } diff --git a/src/main/java/org/heigit/bigspatialdata/ohsome/ohsomeApi/controller/executor/ExecutionUtils.java b/src/main/java/org/heigit/bigspatialdata/ohsome/ohsomeApi/controller/executor/ExecutionUtils.java index 2f62d5cc..a224f24c 100644 --- a/src/main/java/org/heigit/bigspatialdata/ohsome/ohsomeApi/controller/executor/ExecutionUtils.java +++ b/src/main/java/org/heigit/bigspatialdata/ohsome/ohsomeApi/controller/executor/ExecutionUtils.java @@ -3,6 +3,7 @@ import java.text.DecimalFormat; import java.text.DecimalFormatSymbols; import java.util.ArrayList; +import java.util.EnumSet; import java.util.LinkedList; import java.util.List; import java.util.Locale; @@ -19,6 +20,7 @@ import org.heigit.bigspatialdata.oshdb.api.object.OSMContribution; import org.heigit.bigspatialdata.oshdb.api.object.OSMEntitySnapshot; import org.heigit.bigspatialdata.oshdb.osm.OSMEntity; +import org.heigit.bigspatialdata.oshdb.osm.OSMType; import org.heigit.bigspatialdata.oshdb.util.geometry.Geo; import org.heigit.bigspatialdata.oshdb.util.geometry.OSHDBGeometryBuilder; import com.vividsolutions.jts.geom.Geometry; @@ -53,7 +55,26 @@ public String[] shareParamEvaluation(String[] keys2, String[] values2) { if (keys2 == null || keys2.length < 1) throw new BadRequestException( - "You need to define at least one key if you want to use /share."); + "You need to define at least one key if you want to use this resource."); + if (values2 == null) + values2 = new String[0]; + if (keys2.length < values2.length) + throw new BadRequestException( + "There cannot be more input values in values2 than in keys2 as values2n must fit to keys2n."); + + return values2; + } + + /** + * Evaluates the keys2 and values2 String arrays from /ratio requests. + * + * @param keys2 String array containing the provided keys2 parameters. + * @param values2 String array containing the provided values2 parameters. + * @return String array containing the given (or an empty) values2 array, which is + * not null. + */ + public String[] ratioParamEvaluation(String[] keys2, String[] values2) { + if (values2 == null) values2 = new String[0]; if (keys2.length < values2.length) @@ -93,17 +114,7 @@ public Geometry getGeometry(BoundaryType boundary, GeometryBuilder geomBuilder) return geom; } - /** - * Computes the result for the /count|length|perimeter|area/groupBy/boundary resources using the - * map-reduce functions from the OSHDB. - * - * @param requestResource - * @param bType - * @param mapRed - * @param geomBuilder - * @return SortedMap result object. - * @throws Exception - */ + /** Computes the result for the /count|length|perimeter|area/groupBy/boundary resources. */ public SortedMap, ? extends Number> computeCountLengthPerimeterAreaGBB( RequestResource requestResource, BoundaryType bType, MapReducer mapRed, GeometryBuilder geomBuilder, @@ -155,19 +166,7 @@ public Geometry getGeometry(BoundaryType boundary, GeometryBuilder geomBuilder) return result; } - /** - * Computes the result for the /count/share/groupBy/boundary resource using the map-reduce - * functions from the OSHDB. - * - * @param bType - * @param mapRed - * @param keysInt2 - * @param valuesInt2 - * @param geomBuilder - * @return SortedMap result object. - * @throws UnsupportedOperationException - * @throws Exception - */ + /** Computes the result for the /count/share/groupBy/boundary resources. */ public SortedMap>, Integer> computeCountShareGBB( BoundaryType bType, MapReducer mapRed, Integer[] keysInt2, Integer[] valuesInt2, GeometryBuilder geomBuilder) @@ -217,20 +216,7 @@ public SortedMap>, Integer> comput return result; } - /** - * Computes the result for the /length|perimeter|area/share/groupBy/boundary resources using the - * map-reduce functions from the OSHDB. - * - * @param requestResource - * @param bType - * @param mapRed - * @param keysInt2 - * @param valuesInt2 - * @param geomBuilder - * @return SortedMap result object. - * @throws UnsupportedOperationException - * @throws Exception - */ + /** Computes the result for the /count|length|perimeter|area/share/groupBy/boundary resources. */ public SortedMap>, ? extends Number> computeCountLengthPerimeterAreaShareGBB( RequestResource requestResource, BoundaryType bType, MapReducer mapRed, Integer[] keysInt2, Integer[] valuesInt2, GeometryBuilder geomBuilder) @@ -305,17 +291,51 @@ public SortedMap>, Integer> comput } return result; } - - /** - * Internal helper method to get the geometry from an OSMEntitySnapshot object. - */ + + /** Compares an OSMType with an EnumSet of OSMTypes. */ + public boolean isOSMType(EnumSet types, OSMType currentElementType) { + + for (OSMType type : types) + if (currentElementType.equals(type)) { + return true; + } + return false; + } + + /** Compares the OSM type and tag(s) of the given entity to the given types|tags. */ + public boolean entityMatches(OSMEntity entity, EnumSet osmTypes, Integer[] keysInt, + Integer[] valuesInt) { + + boolean matches = true; + if (osmTypes.contains(entity.getType())) { + for (int i = 0; i < keysInt.length; i++) { + boolean matchesTag; + if (i < valuesInt.length) + matchesTag = entity.hasTagValue(keysInt[i], valuesInt[i]); + else + matchesTag = entity.hasTagKey(keysInt[i]); + if (!matchesTag) { + matches = false; + break; + } + } + } else { + matches = false; + } + return matches; + } + + /** Enum type used in /ratio computation. */ + public enum MatchType { + MATCHES1, MATCHES2, MATCHESBOTH + } + + /** Internal helper method to get the geometry from an OSMEntitySnapshot object. */ private Geometry getSnapshotGeom(OSHDBMapReducible f) { return ((OSMEntitySnapshot) f).getGeometry(); } - /** - * Internal helper method to get the geometry from an OSMContribution object. - */ + /** Internal helper method to get the geometry from an OSMContribution object. */ private Geometry getContributionGeom(OSHDBMapReducible f) { Geometry geom = ((OSMContribution) f).getGeometryAfter(); if (geom == null)