From 94ebfd366449f892ee01168cd1419cd77e9896ce Mon Sep 17 00:00:00 2001 From: Carsten Piepel Date: Wed, 1 Mar 2017 15:59:25 -0800 Subject: [PATCH 01/13] Adds clustering SOE project. --- .../attribute-security-filter-soi/pom.xml | 2 +- examples/clustering-soe/pom.xml | 125 ++++++++++++++++++ .../src/assembly/soe-assembly.xml | 39 ++++++ .../src/assembly/soe-config.xml | 51 +++++++ 4 files changed, 216 insertions(+), 1 deletion(-) create mode 100644 examples/clustering-soe/pom.xml create mode 100644 examples/clustering-soe/src/assembly/soe-assembly.xml create mode 100644 examples/clustering-soe/src/assembly/soe-config.xml diff --git a/examples/attribute-security-filter-soi/pom.xml b/examples/attribute-security-filter-soi/pom.xml index e08977d..2c7ea54 100644 --- a/examples/attribute-security-filter-soi/pom.xml +++ b/examples/attribute-security-filter-soi/pom.xml @@ -24,7 +24,7 @@ attribute-security-filter-soi attribute-security-filter-soi - A Server Object Interceptor (SOI) sample to enable feature-level security for query operations + A Server Object Interceptor (SOI) example to enable feature-level security for query operations jar diff --git a/examples/clustering-soe/pom.xml b/examples/clustering-soe/pom.xml new file mode 100644 index 0000000..1740da8 --- /dev/null +++ b/examples/clustering-soe/pom.xml @@ -0,0 +1,125 @@ + + + + 4.0.0 + + com.esri.serverextensions + server-extension-java + 0.2.0 + ../../pom.xml + + clustering-soe + clustering-soe + A Server Object Extension (SOE) example for clustering point data server-side. + jar + + + com.esri.serverextensions + server-extension-core + ${parent.version} + + + + + + org.apache.maven.plugins + maven-jar-plugin + 3.0.2 + + + src/main/assemblies + + + + true + + + + + + org.codehaus.gmaven + groovy-maven-plugin + 2.0 + + + org.safehaus.jug + jug + 2.0.0 + asl + + + + + package + + execute + + + + import org.safehaus.uuid.UUIDGenerator + import java.util.Date + def uuid = UUIDGenerator.getInstance().generateRandomBasedUUID() + project.properties.setProperty('soe.uuid', uuid.toString()) + def date = new Date() + project.properties.setProperty('soe.timestamp', date.format('EEE MMM d k:mm:ss z yyyy')) + project.properties.setProperty('soe.hhmm', date.format('HHmm')) + + + + + + + maven-assembly-plugin + 3.0.0 + + + package + + single + + + true + + src/assembly/soe-assembly.xml + + + + + + + maven-antrun-plugin + 1.8 + + + rename-to-dot-soe + install + + + + + + + run + + + + + + + \ No newline at end of file diff --git a/examples/clustering-soe/src/assembly/soe-assembly.xml b/examples/clustering-soe/src/assembly/soe-assembly.xml new file mode 100644 index 0000000..536d97f --- /dev/null +++ b/examples/clustering-soe/src/assembly/soe-assembly.xml @@ -0,0 +1,39 @@ + + + soe-assembly + + zip + + false + + + Install + true + false + runtime + + + + + src/assembly/soe-config.xml + + true + Config.xml + + + \ No newline at end of file diff --git a/examples/clustering-soe/src/assembly/soe-config.xml b/examples/clustering-soe/src/assembly/soe-config.xml new file mode 100644 index 0000000..344cc4e --- /dev/null +++ b/examples/clustering-soe/src/assembly/soe-config.xml @@ -0,0 +1,51 @@ + + + + ${project.name} + ${project.description} + ${soe.timestamp} + + + + ${project.version} + + + + {${soe.uuid}} + + + MapServer + + + ${project.name} + Attribute Security Filter + ${project.description} + + + + + false + true + true + false + + + + + + + + + From 242d170281097ef44d150aeb83f7f65951f7056c Mon Sep 17 00:00:00 2001 From: Carsten Piepel Date: Wed, 1 Mar 2017 16:14:24 -0800 Subject: [PATCH 02/13] Adds clustering example SOE. Replaces ${parent.version} in POM files with ${project.parent.version} to eliminate deprecation build warnings. --- .../attribute-security-filter-soi/pom.xml | 2 +- examples/clustering-soe/pom.xml | 2 +- .../src/assembly/soe-config.xml | 2 +- .../esri/serverextension/cluster/Cluster.java | 99 +++++ .../cluster/ClusterAssembler.java | 407 ++++++++++++++++++ .../esri/serverextension/cluster/Extent.java | 58 +++ .../esri/serverextension/cluster/Feature.java | 44 ++ .../esri/serverextension/cluster/Point.java | 47 ++ pom.xml | 3 +- server-extension-template/pom.xml | 2 +- 10 files changed, 661 insertions(+), 5 deletions(-) create mode 100644 examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/Cluster.java create mode 100644 examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/ClusterAssembler.java create mode 100644 examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/Extent.java create mode 100644 examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/Feature.java create mode 100644 examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/Point.java diff --git a/examples/attribute-security-filter-soi/pom.xml b/examples/attribute-security-filter-soi/pom.xml index 2c7ea54..9f758e1 100644 --- a/examples/attribute-security-filter-soi/pom.xml +++ b/examples/attribute-security-filter-soi/pom.xml @@ -31,7 +31,7 @@ com.esri.serverextensions server-extension-core - ${parent.version} + ${project.parent.version} diff --git a/examples/clustering-soe/pom.xml b/examples/clustering-soe/pom.xml index 1740da8..aecdfcd 100644 --- a/examples/clustering-soe/pom.xml +++ b/examples/clustering-soe/pom.xml @@ -30,7 +30,7 @@ com.esri.serverextensions server-extension-core - ${parent.version} + ${project.parent.version} diff --git a/examples/clustering-soe/src/assembly/soe-config.xml b/examples/clustering-soe/src/assembly/soe-config.xml index 344cc4e..18cea73 100644 --- a/examples/clustering-soe/src/assembly/soe-config.xml +++ b/examples/clustering-soe/src/assembly/soe-config.xml @@ -28,7 +28,7 @@ MapServer - + ${project.name} Attribute Security Filter ${project.description} diff --git a/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/Cluster.java b/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/Cluster.java new file mode 100644 index 0000000..939b1cf --- /dev/null +++ b/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/Cluster.java @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2017 Esri + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License.​ + */ + +package com.esri.serverextension.cluster; + +import java.util.ArrayList; + +/** + * Created by kcoffin on 2/8/17. + */ +public class Cluster { + + private double _clusterCount; + private Point _point; + private ArrayList _features; + + //A cluster needs to be created with at least one feature + public Cluster(Feature feature){ + _features = new ArrayList<>(); + _point = new Point(feature.getPoint()); + _clusterCount = feature.getValue(); + _features.add(feature); + } + + public Point getPoint(){ + return _point; + } + + public void setClusterCount(double ct){ + _clusterCount = ct; + } + + public void addFeature(Feature feature){ + double value = feature.getValue(); + double count = _clusterCount; + _features.add(feature); + double ptc = value/(count + value); + double ctc = count/(count + value); + Point cluster = _point; + Point p = feature.getPoint(); + + double x = (p.x * ptc + (cluster.x * ctc)); + double y = (p.y * ptc + (cluster.y * ctc)); + cluster.x = x; + cluster.y = y; + _clusterCount += value; + } + + public void addPointCluster(Feature feature, double ptCount){ + double count, x, y; + count = getValue(); + + Point p = feature.getPoint(); + getFeatures().add(feature); + + double ptc = ptCount/(count + ptCount); + double ctc = count/(count + ptCount); + + x = (p.x * ptc + (_point.x * ctc)); + y = (p.y * ptc + (_point.y * ctc)); + _clusterCount += ptCount; + _point.x = x; + _point.y = y; + } + + public double getValue(){ + return _clusterCount; + } + + public ArrayList getFeatures(){ + return _features; + } + + + + + + public void print(){ + System.out.print("Cluster value:"+_clusterCount+" "); + _point.print(); + System.out.println(); + for(Feature f:_features){ + f.print(); + } + System.out.println("=============="); + } + +} diff --git a/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/ClusterAssembler.java b/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/ClusterAssembler.java new file mode 100644 index 0000000..c075c5f --- /dev/null +++ b/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/ClusterAssembler.java @@ -0,0 +1,407 @@ +/* + * Copyright (c) 2017 Esri + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License.​ + */ + +package com.esri.serverextension.cluster; + +import java.util.*; + +/** + * Assembles the cluster + */ +public class ClusterAssembler { + + //The grid's extent + private Extent _extent; + + + //cellsize of grid + private double _cellSize; + + private double _mapUnitsPerPixel; + + //number of columns in grid + private int _numColumns; + + //number of columns in grid + private int _numRows; + + + //the cells which keeps the cluster info + private Map> _cells; + + //the desired cluster distance in pixels + private double _clusterDistanceInPixels; + + //All the clusters which are created + private ArrayList _clusters; + + + /** + * Assemble the cluster + * @param features all the features + * @param mapUnitsPerPixel map units per pixel (like meters per pixel) + * @param clusterDistanceInPixels cluster distance in pixels + * @param extent the extent in real world coordinates + */ + public ClusterAssembler(ArrayList features, double mapUnitsPerPixel, + double clusterDistanceInPixels, Extent extent){ + addAllFeatures(features, mapUnitsPerPixel, clusterDistanceInPixels, extent); + } + + /** + * Retrieves all the clusters + * @return + */ + public ArrayListgetClusters(){ + return _clusters; + } + + + //Adds all the features. Called internally by the ctor + private void addAllFeatures(ArrayList features, double mapUnitsPerPixel, + double clusterDistanceInPixels, Extent extent + ){ + _cells = new HashMap<> (); + _mapUnitsPerPixel = mapUnitsPerPixel; + _clusterDistanceInPixels = clusterDistanceInPixels; + _cellSize = mapUnitsPerPixel * _clusterDistanceInPixels; + _extent = extent; + _numColumns = getGridColumn(extent.getWidth())+1; + _numRows = getGridRow(extent.getHeight())+1; + _clusters = new ArrayList<>(); + + //first sort the features based on the clusterfieldIndex + // Sorting by Lambda + Collections.sort(features, (Feature feature2, Feature feature1)-> + ((Double)feature1.getValue()).compareTo(feature2.getValue())); + + + /* JAVA 1.7 + Collections.sort(features, new Comparator() { + @Override + public int compare(Feature feature2, Feature feature1) + { + return ((Double)feature1.getValue()).compareTo((Double)feature2.getValue()); + } + }); + */ + + for (Feature feature:features){ + addFeature(feature); + } + + + + for (Cluster cluster:_clusters){ + fixCluster(cluster); + } + + + for (Cluster cluster:_clusters){ + fixCluster(cluster); + } + +/* + System.out.println("+++++++++++++++++++++++++++++++++++++"); + for (Cluster cluster:_clusters){ + examineCluster(cluster); + } +*/ +/* + for (Cluster cluster:_clusters){ + cluster.print(); + } +*/ + + } + + + + + /** + * Add a feature + * @param feature + */ + private void addFeature(Feature feature) { + Cluster closestCluster = getClosestCluster(feature.getPoint()); + + if (closestCluster != null) { + addFeatureToCluster(feature, closestCluster); + }else{ + createCluster(feature);//create new cluster + } + + } + + //from yValue real-world, what is the grid row + private int getGridRow(double yValue){ + return (int) Math.floor((yValue-_extent.getYMin())/_cellSize); + } + + //from xValue real-world, what is the grid column + private int getGridColumn(double xValue){ + return (int) Math.floor((xValue-_extent.getXMin())/_cellSize); + } + + //add a feature to an EXISTING cluster + private void addFeatureToCluster(Feature feature, Cluster cluster) { + //remove it from the grid because its coordinates are going to change + removeClusterFromGrid(cluster); + + //add the feature to the cluster + cluster.addFeature(feature); + + //add it back in to the grid + addClusterToGrid(cluster); + } + + //remove a cluster from the grid (it is already in the grid) + private void removeClusterFromGrid(Cluster cluster){ + Point pt = cluster.getPoint(); + int row = getGridRow(pt.y); + int column = getGridColumn(pt.x); + int index = _numRows * row + column; + ArrayList cell = _cells.get(index); + + if (cell != null) { + cell.remove(cluster); + }else{ + System.out.println("Programming error"); + } + } + + //Add the cluster to the grid + private void addClusterToGrid(Cluster cluster){ + Point pt = cluster.getPoint(); + int row = getGridRow(pt.y); + int column = getGridColumn(pt.x); + int index = _numRows * row + column; + ArrayList cell = _cells.get(index); + + if (cell == null) { + cell = new ArrayList<>(); + _cells.put(index, cell); + } + cell.add(cluster); + } + + private void createCluster(Feature feature) { + Cluster cluster = new Cluster(feature); + addClusterToGrid(cluster); + _clusters.add(cluster); + } + + + + + /** + * Gets the closest cluster within the cell distance + * @param pt + * @return + */ + public Cluster getClosestCluster(Point pt){ + int row = getGridRow(pt.y); + int column = getGridColumn(pt.x); + + //should never happen as all features should come from within the extent + if (row < 0 || column < 0 || row>=_numRows || column>=_numColumns){ + System.out.println("There's an error in the query"); + return null; + } + + int yStart = row; + int yEnd = row; + if (row > 0){ + yStart = row-1; + } + if (row < _numRows-1){ + yEnd = row+1; + } + + + int xStart = column; + int xEnd = column; + if (column > 0){ + xStart = column-1; + } + if (column < _numColumns-1){ + xEnd = column+1; + } + + /* + int xStart = (int)(Math.floor((extent.xmin - this._xmin) / this._cellSize)); + int xEnd = (int)(Math.floor((extent.xmax - this._xmin) / this._cellSize)); + int yStart = (int)(Math.floor((extent.ymin - this._ymin) / this._cellSize)); + int yEnd = (int)(Math.floor((extent.ymax - this._ymin) / this._cellSize)); + */ + + Cluster minCluster = null; + double minDis2 = Double.MAX_VALUE; + + for (int x = xStart; x <= xEnd; x++) { + for (int y = yStart; y <= yEnd; y++) { + int index = _numRows * y + x; + ArrayList cell = _cells.get(index); + if (cell != null) { + for (int i = 0; i < cell.size(); i++) { + Cluster cluster = cell.get(i); + double dis2 = pt.squareDistance(cluster.getPoint()); + if (dis2 < minDis2) { + minDis2 = dis2; + minCluster = cluster; + } + } + } + } + } + if (minDis2 > 0){ + minDis2 = Math.sqrt(minDis2); + } + if (minDis2 > _cellSize){ + return null; + } + return minCluster; + } + + + + + //This examines a cluster and determines if all features in it are the closest to it. + public void examineCluster(Cluster cluster){ + + + ArrayList features = cluster.getFeatures(); + for (int k=0;kpom server-extension-core - examples/attribute-security-filter-soi server-extension-template + examples/attribute-security-filter-soi + examples/clustering-soe 1.8 diff --git a/server-extension-template/pom.xml b/server-extension-template/pom.xml index 2823a90..ca0b51f 100644 --- a/server-extension-template/pom.xml +++ b/server-extension-template/pom.xml @@ -29,7 +29,7 @@ com.esri.serverextensions server-extension-core - ${parent.version} + ${project.parent.version} From d71597ebaa6fe5198f4fd6b33092a38909a90b3b Mon Sep 17 00:00:00 2001 From: Carsten Piepel Date: Sun, 5 Mar 2017 14:48:23 -0800 Subject: [PATCH 03/13] Fixes issue with server extensions failing to shut down after they have been initialized but not used. Also changes return value handler to allow additional types. --- .../AbstractRestServerObjectExtension.java | 7 +++++ .../internal/DefaultReturnValueHandler.java | 29 ++++++++++++------- 2 files changed, 26 insertions(+), 10 deletions(-) diff --git a/server-extension-core/src/main/java/com/esri/serverextension/core/server/AbstractRestServerObjectExtension.java b/server-extension-core/src/main/java/com/esri/serverextension/core/server/AbstractRestServerObjectExtension.java index 14ee2c8..6388ca4 100644 --- a/server-extension-core/src/main/java/com/esri/serverextension/core/server/AbstractRestServerObjectExtension.java +++ b/server-extension-core/src/main/java/com/esri/serverextension/core/server/AbstractRestServerObjectExtension.java @@ -82,6 +82,9 @@ protected String getDisplayName() { public final void init(IServerObjectHelper serverObjectHelper) throws IOException, AutomationException { try { + // prevents server extension from failing to shut down + Cleaner.trackObjectsInCurrentThread(); + ServerObjectExtProperties annotation = this.getClass() .getAnnotation(ServerObjectExtProperties.class); isInterceptor = annotation.interceptor(); @@ -105,6 +108,10 @@ public final void shutdown() throws IOException, AutomationException { logger.info("Shutting down ..."); doShutdown(); ((ConfigurableApplicationContext) applicationContext).close(); + + // prevents server extension from failing to shut down + Cleaner.releaseAllInCurrentThread(); + logger.info("Shut down completed."); delegateMappings = null; serverContext = null; diff --git a/server-extension-core/src/main/java/com/esri/serverextension/core/server/internal/DefaultReturnValueHandler.java b/server-extension-core/src/main/java/com/esri/serverextension/core/server/internal/DefaultReturnValueHandler.java index 13df8ce..90fdb62 100644 --- a/server-extension-core/src/main/java/com/esri/serverextension/core/server/internal/DefaultReturnValueHandler.java +++ b/server-extension-core/src/main/java/com/esri/serverextension/core/server/internal/DefaultReturnValueHandler.java @@ -15,6 +15,8 @@ package com.esri.serverextension.core.server.internal; import com.esri.arcgis.geodatabase.IRecordSet; +import com.esri.arcgis.server.json.JSONArray; +import com.esri.arcgis.server.json.JSONObject; import com.esri.serverextension.core.server.RestRequest; import com.esri.serverextension.core.server.RestResponse; import com.esri.serverextension.core.rest.json.JSONConverter; @@ -24,6 +26,7 @@ import org.springframework.core.MethodParameter; import javax.xml.bind.annotation.XmlRootElement; +import java.util.Collection; public final class DefaultReturnValueHandler implements ReturnValueHandler { @@ -41,7 +44,7 @@ public RestResponse handleReturnValue(Object returnValue, .getAnnotation(XmlRootElement.class); if (RestResponse.class.isAssignableFrom(type)) { return (RestResponse) returnValue; - } else if (String.class.isAssignableFrom(type)) { + } else if (CharSequence.class.isAssignableFrom(type)) { return new RestResponse(null, JSONConverter.toByteArray((String) returnValue)); } else if (xmlRootElement != null) { @@ -50,14 +53,17 @@ public RestResponse handleReturnValue(Object returnValue, } else if (IRecordSet.class.isAssignableFrom(type)) { return new RestResponse(null, objectMapper.writeValueAsBytes(returnValue)); - } else if (IJSONObject.class.isAssignableFrom(type)) { + } else if (JSONObject.class.isAssignableFrom(type)) { return new RestResponse(null, - JSONConverter.toByteArray(((IJSONObject) returnValue) - .toJSONString(null))); - } else if (IJSONArray.class.isAssignableFrom(type)) { + JSONConverter.toByteArray(((JSONObject) returnValue) + .toString())); + } else if (JSONArray.class.isAssignableFrom(type)) { return new RestResponse(null, - JSONConverter.toByteArray(((IJSONArray) returnValue) - .toJSONString(null))); + JSONConverter.toByteArray(((JSONArray) returnValue) + .toString())); + } else if (Collection.class.isAssignableFrom(type)) { + return new RestResponse(null, + objectMapper.writeValueAsBytes(returnValue)); } throw new IllegalArgumentException(String.format( "Cannot handle return value: %1$s", returnValue)); @@ -70,12 +76,15 @@ public boolean handles(MethodParameter parameter) { "Method parameter must be a return value."); } Class type = parameter.getParameterType(); - return RestResponse.class.isAssignableFrom(type) + return RestRequest.class.isAssignableFrom(type) + || RestResponse.class.isAssignableFrom(type) + || CharSequence.class.isAssignableFrom(type) || IRecordSet.class.isAssignableFrom(type) || type.getAnnotation(XmlRootElement.class) != null || IRecordSet.class.isAssignableFrom(type) - || IJSONObject.class.isAssignableFrom(type) - || IJSONArray.class.isAssignableFrom(type); + || JSONObject.class.isAssignableFrom(type) + || JSONArray.class.isAssignableFrom(type) + || Collection.class.isAssignableFrom(type); } } From 0764149e1ff5d83e3ad6a69c163b0302fb3bded9 Mon Sep 17 00:00:00 2001 From: Carsten Piepel Date: Sun, 5 Mar 2017 14:51:26 -0800 Subject: [PATCH 04/13] Adds IntelliJ .iml files to .gitignore. --- .gitignore | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.gitignore b/.gitignore index ec7e95c..7aa54f2 100644 --- a/.gitignore +++ b/.gitignore @@ -41,3 +41,13 @@ com_crashlytics_export_strings.xml crashlytics.properties crashlytics-build.properties fabric.properties +.idea/ +examples/attribute-security-filter-soi/target/ +examples/clustering-soe/clustering-soe.iml +examples/clustering-soe/target/ +server-extension-core/server-extension-core.iml +server-extension-core/target/ +server-extension-java.iml +server-extension-template/server-extension-template.iml +server-extension-template/target/ +target/ From b268dd5fc3c92754caaa2ac37d63ca0479176bec Mon Sep 17 00:00:00 2001 From: Carsten Piepel Date: Tue, 7 Mar 2017 11:06:45 -0800 Subject: [PATCH 05/13] Initial running version of cluster-soe, sans clustering functionality. --- .../src/assembly/soe-assembly.xml | 1 + .../src/assembly/soe-config.xml | 2 +- .../AttributeSecurityFilterConfig.java | 2 +- examples/clustering-soe/pom.xml | 26 ++++ .../src/assembly/soe-assembly.xml | 1 + .../src/assembly/soe-config.xml | 10 +- .../cluster/AboutResource.java | 72 ++++++++++ .../esri/serverextension/cluster/Cluster.java | 33 ++--- .../cluster/ClusterAssembler.java | 136 +++++++++--------- .../cluster/ClusteringConfig.java | 82 +++++++++++ .../cluster/ClusteringExtension.java | 90 ++++++++++++ .../esri/serverextension/cluster/Extent.java | 20 ++- .../esri/serverextension/cluster/Feature.java | 10 +- .../cluster/MapServerUtilities.java | 66 +++++++++ .../esri/serverextension/cluster/Point.java | 18 +-- .../cluster/QueryOperationInput.java | 42 ++++++ .../serverextension/cluster/RootResource.java | 61 ++++++++ .../src/main/resources/buildInfo.properties | 3 + .../src/main/resources/git.properties | 21 +++ .../core/server/DefaultConfig.java | 2 +- 20 files changed, 583 insertions(+), 115 deletions(-) create mode 100644 examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/AboutResource.java create mode 100644 examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/ClusteringConfig.java create mode 100644 examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/ClusteringExtension.java create mode 100644 examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/MapServerUtilities.java create mode 100644 examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/QueryOperationInput.java create mode 100644 examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/RootResource.java create mode 100644 examples/clustering-soe/src/main/resources/buildInfo.properties create mode 100644 examples/clustering-soe/src/main/resources/git.properties diff --git a/examples/attribute-security-filter-soi/src/assembly/soe-assembly.xml b/examples/attribute-security-filter-soi/src/assembly/soe-assembly.xml index 536d97f..434b784 100644 --- a/examples/attribute-security-filter-soi/src/assembly/soe-assembly.xml +++ b/examples/attribute-security-filter-soi/src/assembly/soe-assembly.xml @@ -1,3 +1,4 @@ + - ${project.name} ${project.description} diff --git a/examples/attribute-security-filter-soi/src/main/java/com/esri/serverextension/attributesecurityfilter/AttributeSecurityFilterConfig.java b/examples/attribute-security-filter-soi/src/main/java/com/esri/serverextension/attributesecurityfilter/AttributeSecurityFilterConfig.java index 8526d67..656b1c4 100644 --- a/examples/attribute-security-filter-soi/src/main/java/com/esri/serverextension/attributesecurityfilter/AttributeSecurityFilterConfig.java +++ b/examples/attribute-security-filter-soi/src/main/java/com/esri/serverextension/attributesecurityfilter/AttributeSecurityFilterConfig.java @@ -17,7 +17,7 @@ import org.springframework.context.annotation.Configuration; @Configuration -@ComponentScan("com.esri.arcgis.soi.attributesecurityfilter") +@ComponentScan("com.esri.serverextension.attributesecurityfilter") public class AttributeSecurityFilterConfig { public AttributeSecurityFilterConfig() { diff --git a/examples/clustering-soe/pom.xml b/examples/clustering-soe/pom.xml index aecdfcd..6fe1e88 100644 --- a/examples/clustering-soe/pom.xml +++ b/examples/clustering-soe/pom.xml @@ -34,6 +34,16 @@ + + + + src/main/resources + true + + **/*.properties + + + org.apache.maven.plugins @@ -120,6 +130,22 @@ + + pl.project13.maven + git-commit-id-plugin + 2.2.2 + + + + revision + + + + + ${project.basedir}/../../.git + true + + \ No newline at end of file diff --git a/examples/clustering-soe/src/assembly/soe-assembly.xml b/examples/clustering-soe/src/assembly/soe-assembly.xml index 536d97f..434b784 100644 --- a/examples/clustering-soe/src/assembly/soe-assembly.xml +++ b/examples/clustering-soe/src/assembly/soe-assembly.xml @@ -1,3 +1,4 @@ + - ${project.name} ${project.description} @@ -28,9 +28,9 @@ MapServer - - ${project.name} - Attribute Security Filter + + clustering + Clustering ${project.description} @@ -38,7 +38,7 @@ false true - true + false false diff --git a/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/AboutResource.java b/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/AboutResource.java new file mode 100644 index 0000000..7e3a0a1 --- /dev/null +++ b/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/AboutResource.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2017 Esri + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License.​ + */ + +package com.esri.serverextension.cluster; + +import javax.annotation.Resource; + +import org.springframework.stereotype.Service; +import org.springframework.web.bind.annotation.RequestMapping; + +import com.esri.arcgis.server.json.JSONObject; + +@Service +public class AboutResource { + + private String projectName; + private String projectDescription; + private String projectVersion; + private String gitBranch; + private String gitCommitID; + + public AboutResource() { + } + + @Resource(name = "projectName") + public void setProjectName(String projectName) { + this.projectName = projectName; + } + + @Resource(name = "projectDescription") + public void setProjectDescription(String projectDescription) { + this.projectDescription = projectDescription; + } + + @Resource(name = "projectVersion") + public void setProjectVersion(String projectVersion) { + this.projectVersion = projectVersion; + } + + @Resource(name = "gitBranch") + public void setGitBranch(String gitBranch) { + this.gitBranch = gitBranch; + } + + @Resource(name = "gitCommitID") + public void setGitCommitID(String gitCommitID) { + this.gitCommitID = gitCommitID; + } + + @RequestMapping("/about") + public JSONObject getAboutResource() { + JSONObject aboutResource = new JSONObject(); + aboutResource.put("name", projectName); + aboutResource.put("description", projectDescription); + aboutResource.put("version", projectVersion); + aboutResource.put("gitBranch", gitBranch); + aboutResource.put("gitCommitID", gitCommitID); + return aboutResource; + } + +} diff --git a/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/Cluster.java b/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/Cluster.java index 939b1cf..1a71e80 100644 --- a/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/Cluster.java +++ b/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/Cluster.java @@ -22,31 +22,31 @@ public class Cluster { private double _clusterCount; - private Point _point; + private Point _point; private ArrayList _features; //A cluster needs to be created with at least one feature - public Cluster(Feature feature){ + public Cluster(Feature feature) { _features = new ArrayList<>(); _point = new Point(feature.getPoint()); _clusterCount = feature.getValue(); _features.add(feature); } - public Point getPoint(){ + public Point getPoint() { return _point; } - public void setClusterCount(double ct){ + public void setClusterCount(double ct) { _clusterCount = ct; } - public void addFeature(Feature feature){ + public void addFeature(Feature feature) { double value = feature.getValue(); double count = _clusterCount; _features.add(feature); - double ptc = value/(count + value); - double ctc = count/(count + value); + double ptc = value / (count + value); + double ctc = count / (count + value); Point cluster = _point; Point p = feature.getPoint(); @@ -57,15 +57,15 @@ public void addFeature(Feature feature){ _clusterCount += value; } - public void addPointCluster(Feature feature, double ptCount){ + public void addPointCluster(Feature feature, double ptCount) { double count, x, y; count = getValue(); Point p = feature.getPoint(); getFeatures().add(feature); - double ptc = ptCount/(count + ptCount); - double ctc = count/(count + ptCount); + double ptc = ptCount / (count + ptCount); + double ctc = count / (count + ptCount); x = (p.x * ptc + (_point.x * ctc)); y = (p.y * ptc + (_point.y * ctc)); @@ -74,23 +74,20 @@ public void addPointCluster(Feature feature, double ptCount){ _point.y = y; } - public double getValue(){ + public double getValue() { return _clusterCount; } - public ArrayList getFeatures(){ + public ArrayList getFeatures() { return _features; } - - - - public void print(){ - System.out.print("Cluster value:"+_clusterCount+" "); + public void print() { + System.out.print("Cluster value:" + _clusterCount + " "); _point.print(); System.out.println(); - for(Feature f:_features){ + for (Feature f : _features) { f.print(); } System.out.println("=============="); diff --git a/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/ClusterAssembler.java b/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/ClusterAssembler.java index c075c5f..ad6ec95 100644 --- a/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/ClusterAssembler.java +++ b/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/ClusterAssembler.java @@ -14,7 +14,10 @@ package com.esri.serverextension.cluster; -import java.util.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; /** * Assembles the cluster @@ -31,10 +34,10 @@ public class ClusterAssembler { private double _mapUnitsPerPixel; //number of columns in grid - private int _numColumns; + private int _numColumns; //number of columns in grid - private int _numRows; + private int _numRows; //the cells which keeps the cluster info @@ -49,21 +52,23 @@ public class ClusterAssembler { /** * Assemble the cluster - * @param features all the features - * @param mapUnitsPerPixel map units per pixel (like meters per pixel) + * + * @param features all the features + * @param mapUnitsPerPixel map units per pixel (like meters per pixel) * @param clusterDistanceInPixels cluster distance in pixels - * @param extent the extent in real world coordinates + * @param extent the extent in real world coordinates */ public ClusterAssembler(ArrayList features, double mapUnitsPerPixel, - double clusterDistanceInPixels, Extent extent){ + double clusterDistanceInPixels, Extent extent) { addAllFeatures(features, mapUnitsPerPixel, clusterDistanceInPixels, extent); } /** * Retrieves all the clusters + * * @return */ - public ArrayListgetClusters(){ + public ArrayList getClusters() { return _clusters; } @@ -71,20 +76,20 @@ public ClusterAssembler(ArrayList features, double mapUnitsPerPixel, //Adds all the features. Called internally by the ctor private void addAllFeatures(ArrayList features, double mapUnitsPerPixel, double clusterDistanceInPixels, Extent extent - ){ - _cells = new HashMap<> (); + ) { + _cells = new HashMap<>(); _mapUnitsPerPixel = mapUnitsPerPixel; _clusterDistanceInPixels = clusterDistanceInPixels; _cellSize = mapUnitsPerPixel * _clusterDistanceInPixels; _extent = extent; - _numColumns = getGridColumn(extent.getWidth())+1; - _numRows = getGridRow(extent.getHeight())+1; + _numColumns = getGridColumn(extent.getWidth()) + 1; + _numRows = getGridRow(extent.getHeight()) + 1; _clusters = new ArrayList<>(); //first sort the features based on the clusterfieldIndex // Sorting by Lambda - Collections.sort(features, (Feature feature2, Feature feature1)-> - ((Double)feature1.getValue()).compareTo(feature2.getValue())); + Collections.sort(features, (Feature feature2, Feature feature1) -> + ((Double) feature1.getValue()).compareTo(feature2.getValue())); /* JAVA 1.7 @@ -97,18 +102,17 @@ public int compare(Feature feature2, Feature feature1) }); */ - for (Feature feature:features){ + for (Feature feature : features) { addFeature(feature); } - - for (Cluster cluster:_clusters){ + for (Cluster cluster : _clusters) { fixCluster(cluster); } - for (Cluster cluster:_clusters){ + for (Cluster cluster : _clusters) { fixCluster(cluster); } @@ -127,10 +131,9 @@ public int compare(Feature feature2, Feature feature1) } - - /** * Add a feature + * * @param feature */ private void addFeature(Feature feature) { @@ -138,20 +141,20 @@ private void addFeature(Feature feature) { if (closestCluster != null) { addFeatureToCluster(feature, closestCluster); - }else{ + } else { createCluster(feature);//create new cluster } } //from yValue real-world, what is the grid row - private int getGridRow(double yValue){ - return (int) Math.floor((yValue-_extent.getYMin())/_cellSize); + private int getGridRow(double yValue) { + return (int) Math.floor((yValue - _extent.getYMin()) / _cellSize); } //from xValue real-world, what is the grid column - private int getGridColumn(double xValue){ - return (int) Math.floor((xValue-_extent.getXMin())/_cellSize); + private int getGridColumn(double xValue) { + return (int) Math.floor((xValue - _extent.getXMin()) / _cellSize); } //add a feature to an EXISTING cluster @@ -167,7 +170,7 @@ private void addFeatureToCluster(Feature feature, Cluster cluster) { } //remove a cluster from the grid (it is already in the grid) - private void removeClusterFromGrid(Cluster cluster){ + private void removeClusterFromGrid(Cluster cluster) { Point pt = cluster.getPoint(); int row = getGridRow(pt.y); int column = getGridColumn(pt.x); @@ -176,13 +179,13 @@ private void removeClusterFromGrid(Cluster cluster){ if (cell != null) { cell.remove(cluster); - }else{ + } else { System.out.println("Programming error"); } } //Add the cluster to the grid - private void addClusterToGrid(Cluster cluster){ + private void addClusterToGrid(Cluster cluster) { Point pt = cluster.getPoint(); int row = getGridRow(pt.y); int column = getGridColumn(pt.x); @@ -203,40 +206,39 @@ private void createCluster(Feature feature) { } - - /** * Gets the closest cluster within the cell distance + * * @param pt * @return */ - public Cluster getClosestCluster(Point pt){ + public Cluster getClosestCluster(Point pt) { int row = getGridRow(pt.y); int column = getGridColumn(pt.x); //should never happen as all features should come from within the extent - if (row < 0 || column < 0 || row>=_numRows || column>=_numColumns){ + if (row < 0 || column < 0 || row >= _numRows || column >= _numColumns) { System.out.println("There's an error in the query"); return null; } int yStart = row; int yEnd = row; - if (row > 0){ - yStart = row-1; + if (row > 0) { + yStart = row - 1; } - if (row < _numRows-1){ - yEnd = row+1; + if (row < _numRows - 1) { + yEnd = row + 1; } int xStart = column; int xEnd = column; - if (column > 0){ - xStart = column-1; + if (column > 0) { + xStart = column - 1; } - if (column < _numColumns-1){ - xEnd = column+1; + if (column < _numColumns - 1) { + xEnd = column + 1; } /* @@ -265,43 +267,39 @@ public Cluster getClosestCluster(Point pt){ } } } - if (minDis2 > 0){ + if (minDis2 > 0) { minDis2 = Math.sqrt(minDis2); } - if (minDis2 > _cellSize){ + if (minDis2 > _cellSize) { return null; } return minCluster; } - - //This examines a cluster and determines if all features in it are the closest to it. - public void examineCluster(Cluster cluster){ + public void examineCluster(Cluster cluster) { ArrayList features = cluster.getFeatures(); - for (int k=0;k getPointFeatureLayers( + ServerObjectExtensionContext serverContext) { + try { + List layerInfoList = new ArrayList<>(); + IMapServerDataAccess mapServerDataAccess = (IMapServerDataAccess) serverContext + .getServerObject(); + IMapServer3 mapServer = (IMapServer3) mapServerDataAccess; + String mapName = mapServer.getDefaultMapName(); + IMapServerInfo4 mapServerInfo = (IMapServerInfo4) mapServer + .getServerInfo(mapName); + IMapLayerInfos layerInfos = mapServerInfo.getMapLayerInfos(); + int layerCount = layerInfos.getCount(); + for (int i = 0; i < layerCount; i++) { + IMapLayerInfo layerInfo = layerInfos.getElement(i); + if (layerInfo.isComposite()) { + continue; + } + if (layerInfo.isFeatureLayer()) { + Object dataSource = mapServerDataAccess + .getDisplayDataSource(mapName, layerInfo.getID()); + IFeatureClass featureClass = new FeatureClass(dataSource); + int geometryType = featureClass.getShapeType(); + if (geometryType == esriGeometryType.esriGeometryPoint) { + layerInfoList.add(layerInfo); + } + Cleaner.release(featureClass); + } + } + return layerInfoList; + } catch (IOException ex) { + throw new ArcObjectsInteropException( + "Failed to get point feature layers from map server object."); + } + } + +} diff --git a/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/Point.java b/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/Point.java index 2579060..46d516b 100644 --- a/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/Point.java +++ b/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/Point.java @@ -20,28 +20,30 @@ public class Point { public double x; public double y; - public Point(double x, double y){ + + public Point(double x, double y) { this.x = x; this.y = y; } - public Point(Point pt){ + + public Point(Point pt) { x = pt.x; y = pt.y; } - public double squareDistance(Point pt){ + public double squareDistance(Point pt) { double dx = pt.x - x; double dy = pt.y - y; - return (dx*dx) + (dy*dy); + return (dx * dx) + (dy * dy); } - public double distance(Point pt){ + + public double distance(Point pt) { return Math.sqrt(squareDistance(pt)); } - - public void print(){ - System.out.print("("+x+","+y+")"); + public void print() { + System.out.print("(" + x + "," + y + ")"); } } diff --git a/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/QueryOperationInput.java b/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/QueryOperationInput.java new file mode 100644 index 0000000..34b62a7 --- /dev/null +++ b/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/QueryOperationInput.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2017 Esri + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License.​ + */ + +package com.esri.serverextension.cluster; + +import javax.xml.bind.annotation.XmlRootElement; + +import com.esri.arcgis.geometry.IGeometry; +import com.esri.arcgis.geometry.ISpatialReference; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.esri.arcgis.geometry.esriGeometryType; +import com.esri.arcgis.geodatabase.esriSpatialRelEnum; + +@XmlRootElement +@JsonIgnoreProperties(ignoreUnknown = true) +public class QueryOperationInput { + + private static final long serialVersionUID = 1L; + + public QueryOperationInput() { + } + + private String text; + private IGeometry geometry; + private esriGeometryType geometryType; + private ISpatialReference inSR; + private esriSpatialRelEnum spatialRel; + private String relationParam; + + +} diff --git a/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/RootResource.java b/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/RootResource.java new file mode 100644 index 0000000..06000d1 --- /dev/null +++ b/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/RootResource.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2017 Esri + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License.​ + */ + +package com.esri.serverextension.cluster; + +import com.esri.arcgis.carto.IMapLayerInfo; +import com.esri.serverextension.core.server.ServerObjectExtensionContext; +import com.esri.serverextension.core.util.ArcObjectsInteropException; +import org.springframework.stereotype.Component; +import org.springframework.stereotype.Service; +import org.springframework.web.bind.annotation.RequestMapping; + +import com.esri.arcgis.interop.extn.ServerObjectExtProperties; +import com.esri.arcgis.server.json.JSONArray; +import com.esri.arcgis.server.json.JSONObject; + +import java.io.IOException; +import java.util.List; + +@Service +public class RootResource { + + @RequestMapping("/") + public JSONObject getRootResource(ServerObjectExtensionContext serverContext) { + ServerObjectExtProperties annotation = ClusteringExtension.class + .getAnnotation(ServerObjectExtProperties.class); + + JSONObject rootResource = new JSONObject(); + rootResource.put("name", annotation.displayName()); + rootResource.put("description", annotation.description()); + + JSONArray layersArray = new JSONArray(); + rootResource.put("layers", layersArray); + + List pointFeatureLayers = MapServerUtilities.getPointFeatureLayers(serverContext); + for (IMapLayerInfo layerInfo : pointFeatureLayers) { + try { + JSONObject siteLayer = new JSONObject(); + siteLayer.put("name", layerInfo.getName()); + siteLayer.put("id", layerInfo.getID()); + siteLayer.put("description", layerInfo.getDescription()); + layersArray.put(siteLayer); + } catch (IOException ex) { + throw new ArcObjectsInteropException( + "Failed to get details from map layer info."); + } + } + return rootResource; + } +} diff --git a/examples/clustering-soe/src/main/resources/buildInfo.properties b/examples/clustering-soe/src/main/resources/buildInfo.properties new file mode 100644 index 0000000..dcf6ef2 --- /dev/null +++ b/examples/clustering-soe/src/main/resources/buildInfo.properties @@ -0,0 +1,3 @@ +project.name=${project.name} +project.description=${project.description} +project.version=${project.version} diff --git a/examples/clustering-soe/src/main/resources/git.properties b/examples/clustering-soe/src/main/resources/git.properties new file mode 100644 index 0000000..d5e4218 --- /dev/null +++ b/examples/clustering-soe/src/main/resources/git.properties @@ -0,0 +1,21 @@ +git.tags=${git.tags} +git.branch=${git.branch} +git.dirty=${git.dirty} +git.remote.origin.url=${git.remote.origin.url} +git.commit.id=${git.commit.id} +git.commit.id.abbrev=${git.commit.id.abbrev} +git.commit.id.describe=${git.commit.id.describe} +git.commit.id.describe-short=${git.commit.id.describe-short} +git.commit.user.name=${git.commit.user.name} +git.commit.user.email=${git.commit.user.email} +git.commit.message.full=${git.commit.message.full} +git.commit.message.short=${git.commit.message.short} +git.commit.time=${git.commit.time} +git.closest.tag.name=${git.closest.tag.name} +git.closest.tag.commit.count=${git.closest.tag.commit.count} + +git.build.user.name=${git.build.user.name} +git.build.user.email=${git.build.user.email} +git.build.time=${git.build.time} +git.build.host=${git.build.host} +git.build.version=${git.build.version} \ No newline at end of file diff --git a/server-extension-core/src/main/java/com/esri/serverextension/core/server/DefaultConfig.java b/server-extension-core/src/main/java/com/esri/serverextension/core/server/DefaultConfig.java index ca32330..fbf08a9 100644 --- a/server-extension-core/src/main/java/com/esri/serverextension/core/server/DefaultConfig.java +++ b/server-extension-core/src/main/java/com/esri/serverextension/core/server/DefaultConfig.java @@ -26,7 +26,7 @@ import javax.inject.Singleton; @Configuration -@ComponentScan("com.esri.arcgis.soe.template") +@ComponentScan("com.esri.serverextension.core") public class DefaultConfig { @Inject From 7034061fd86c2db632f83f184ef53f42815c61e4 Mon Sep 17 00:00:00 2001 From: Carsten Piepel Date: Tue, 7 Mar 2017 12:01:13 -0800 Subject: [PATCH 06/13] Adds default to path variable resolution when no name is provided as part of @PathVariable annotation. --- .../internal/PathVariableArgumentResolver.java | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/server-extension-core/src/main/java/com/esri/serverextension/core/server/internal/PathVariableArgumentResolver.java b/server-extension-core/src/main/java/com/esri/serverextension/core/server/internal/PathVariableArgumentResolver.java index 2dd5fa5..bee0a6d 100644 --- a/server-extension-core/src/main/java/com/esri/serverextension/core/server/internal/PathVariableArgumentResolver.java +++ b/server-extension-core/src/main/java/com/esri/serverextension/core/server/internal/PathVariableArgumentResolver.java @@ -16,8 +16,10 @@ import com.esri.serverextension.core.server.RestDelegate; import com.esri.serverextension.core.server.RestRequest; +import org.apache.commons.lang3.StringUtils; import org.springframework.beans.SimpleTypeConverter; import org.springframework.core.MethodParameter; +import org.springframework.util.CollectionUtils; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.util.UriTemplate; @@ -50,12 +52,24 @@ public Object resolveArgument(MethodParameter parameter, if (uriTemplate.matches(path)) { Map uriTemplateVars = uriTemplate.match(path); if (uriTemplateVars != null) { - value = uriTemplateVars.get(variable); + if (StringUtils.isEmpty(variable)) { + // PathVariable does not contain name + // get first value in list + if (!CollectionUtils.isEmpty(uriTemplateVars)) { + value = uriTemplateVars.values().iterator().next(); + } + } else { + value = uriTemplateVars.get(variable); + } break; } } } + if (value == null) { + return value; + } + SimpleTypeConverter converter = new SimpleTypeConverter(); return converter.convertIfNecessary(value, type, parameter); } From 1f96255f6afbd4e75e4ba44521ea234cde362063 Mon Sep 17 00:00:00 2001 From: Carsten Piepel Date: Tue, 7 Mar 2017 12:01:46 -0800 Subject: [PATCH 07/13] Adds layer resource. --- .../cluster/LayerResource.java | 44 ++++++++++++++++ .../cluster/LayersResource.java | 51 +++++++++++++++++++ .../cluster/MapServerUtilities.java | 15 ++++++ .../serverextension/cluster/RootResource.java | 23 ++++----- 4 files changed, 120 insertions(+), 13 deletions(-) create mode 100644 examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/LayerResource.java create mode 100644 examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/LayersResource.java diff --git a/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/LayerResource.java b/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/LayerResource.java new file mode 100644 index 0000000..8018d5c --- /dev/null +++ b/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/LayerResource.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2017 Esri + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License.​ + */ + +package com.esri.serverextension.cluster; + +import com.esri.arcgis.carto.IMapLayerInfo; +import com.esri.arcgis.server.json.JSONObject; +import com.esri.serverextension.core.server.ServerObjectExtensionContext; +import com.esri.serverextension.core.util.ArcObjectsInteropException; +import org.springframework.stereotype.Service; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; + +import java.io.IOException; + +@Service +public class LayerResource { + + @RequestMapping("/layers/{layerId}") + public JSONObject getLayerResource(@PathVariable("layerId") int layerId, ServerObjectExtensionContext serverContext) { + IMapLayerInfo layerInfo = MapServerUtilities.getPointFeatureLayerByID(layerId, serverContext); + JSONObject layerObject = new JSONObject(); + try { + layerObject.put("name", layerInfo.getName()); + layerObject.put("id", layerInfo.getID()); + layerObject.put("description", layerInfo.getDescription()); + } catch (IOException ex) { + throw new ArcObjectsInteropException( + String.format("Failed to get details for layer: %1$d", layerId)); + } + return layerObject; + } +} diff --git a/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/LayersResource.java b/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/LayersResource.java new file mode 100644 index 0000000..c99568a --- /dev/null +++ b/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/LayersResource.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2017 Esri + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License.​ + */ + +package com.esri.serverextension.cluster; + +import com.esri.arcgis.carto.IMapLayerInfo; +import com.esri.arcgis.server.json.JSONArray; +import com.esri.arcgis.server.json.JSONObject; +import com.esri.serverextension.core.server.ServerObjectExtensionContext; +import com.esri.serverextension.core.util.ArcObjectsInteropException; +import org.springframework.stereotype.Service; +import org.springframework.web.bind.annotation.RequestMapping; + +import java.io.IOException; +import java.util.List; + +@Service +public class LayersResource { + + @RequestMapping("/layers") + public JSONObject getLayersResource(ServerObjectExtensionContext serverContext) { + JSONArray layersArray = new JSONArray(); + List pointFeatureLayers = MapServerUtilities.getPointFeatureLayers(serverContext); + for (IMapLayerInfo layerInfo : pointFeatureLayers) { + try { + JSONObject layer = new JSONObject(); + layer.put("name", layerInfo.getName()); + layer.put("id", layerInfo.getID()); + layer.put("description", layerInfo.getDescription()); + layersArray.put(layer); + } catch (IOException ex) { + throw new ArcObjectsInteropException( + "Failed to get details from map layer info."); + } + } + JSONObject response = new JSONObject(); + response.put("layers", layersArray); + return response; + } +} diff --git a/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/MapServerUtilities.java b/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/MapServerUtilities.java index a6366b9..48b643c 100644 --- a/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/MapServerUtilities.java +++ b/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/MapServerUtilities.java @@ -63,4 +63,19 @@ public static final List getPointFeatureLayers( } } + public static final IMapLayerInfo getPointFeatureLayerByID(int id, + ServerObjectExtensionContext serverContext) { + List layerInfoList = getPointFeatureLayers(serverContext); + for (IMapLayerInfo layerInfo : layerInfoList) { + try { + if (layerInfo.getID() == id) { + return layerInfo; + } + } catch (IOException ex) { + throw new ArcObjectsInteropException( + String.format("Failed to access point feature layer: %1$d", id)); + } + } + throw new IllegalArgumentException(String.format("No such point feature layer: %1$d", id)); + } } diff --git a/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/RootResource.java b/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/RootResource.java index 06000d1..f540757 100644 --- a/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/RootResource.java +++ b/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/RootResource.java @@ -15,16 +15,14 @@ package com.esri.serverextension.cluster; import com.esri.arcgis.carto.IMapLayerInfo; +import com.esri.arcgis.interop.extn.ServerObjectExtProperties; +import com.esri.arcgis.server.json.JSONArray; +import com.esri.arcgis.server.json.JSONObject; import com.esri.serverextension.core.server.ServerObjectExtensionContext; import com.esri.serverextension.core.util.ArcObjectsInteropException; -import org.springframework.stereotype.Component; import org.springframework.stereotype.Service; import org.springframework.web.bind.annotation.RequestMapping; -import com.esri.arcgis.interop.extn.ServerObjectExtProperties; -import com.esri.arcgis.server.json.JSONArray; -import com.esri.arcgis.server.json.JSONObject; - import java.io.IOException; import java.util.List; @@ -41,21 +39,20 @@ public JSONObject getRootResource(ServerObjectExtensionContext serverContext) { rootResource.put("description", annotation.description()); JSONArray layersArray = new JSONArray(); - rootResource.put("layers", layersArray); - List pointFeatureLayers = MapServerUtilities.getPointFeatureLayers(serverContext); for (IMapLayerInfo layerInfo : pointFeatureLayers) { try { - JSONObject siteLayer = new JSONObject(); - siteLayer.put("name", layerInfo.getName()); - siteLayer.put("id", layerInfo.getID()); - siteLayer.put("description", layerInfo.getDescription()); - layersArray.put(siteLayer); + JSONObject layer = new JSONObject(); + layer.put("name", layerInfo.getName()); + layer.put("id", layerInfo.getID()); + layer.put("description", layerInfo.getDescription()); + layersArray.put(layer); } catch (IOException ex) { throw new ArcObjectsInteropException( - "Failed to get details from map layer info."); + "Failed to get details from map layer info."); } } + rootResource.put("layers", layersArray); return rootResource; } } From 2bb443c2ce292e021a77e95af4c274ec4099d466 Mon Sep 17 00:00:00 2001 From: Carsten Piepel Date: Tue, 7 Mar 2017 12:03:32 -0800 Subject: [PATCH 08/13] Adds attribute-security-filter-soi.iml to .gitignore. --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 7aa54f2..695ec70 100644 --- a/.gitignore +++ b/.gitignore @@ -42,6 +42,7 @@ crashlytics.properties crashlytics-build.properties fabric.properties .idea/ +examples/attribute-security-filter-soi/attribute-security-filter-soi.iml examples/attribute-security-filter-soi/target/ examples/clustering-soe/clustering-soe.iml examples/clustering-soe/target/ From a698cf7d805091793940fedbb5098c5efc879d89 Mon Sep 17 00:00:00 2001 From: Carsten Piepel Date: Tue, 7 Mar 2017 19:16:02 -0800 Subject: [PATCH 09/13] Runs but consumes a lot of memory. --- .../attribute-security-filter-soi.iml | 19 +- .../cluster/AboutResource.java | 5 +- .../esri/serverextension/cluster/Cluster.java | 87 +++--- .../cluster/ClusterAssembler.java | 259 ++++++++---------- .../ClusterAssemblerCallbackHandler.java | 63 +++++ .../cluster/ClusterLayerResource.java | 165 +++++++++++ ...source.java => ClusterLayersResource.java} | 2 +- .../serverextension/cluster/ClusterPoint.java | 78 ++++++ .../cluster/ClusterQueryOperationInput.java | 68 +++++ .../cluster/ClusteringExtension.java | 18 +- .../esri/serverextension/cluster/Extent.java | 64 ----- .../cluster/LayerResource.java | 44 --- .../cluster/MapServerUtilities.java | 33 +++ .../esri/serverextension/cluster/Point.java | 49 ---- .../cluster/QueryOperationInput.java | 42 --- server-extension-core/pom.xml | 10 +- .../GeodatabaseCursorExtractor.java | 28 ++ .../GeodatabaseObjectCallbackHandler.java | 30 ++ ...eObjectCallbackHandlerCursorExtractor.java | 137 +++++++++ .../geodatabase/GeodatabaseObjectMapper.java | 32 +-- ...eodatabaseObjectMapperCursorExtractor.java | 143 ++++++++++ .../GeodatabaseSystemException.java | 33 +++ .../core/geodatabase/GeodatabaseTemplate.java | 87 ++++++ .../core/rest/api/DimensionalDefinition.java | 6 + .../serverextension/core/rest/api/Extent.java | 82 ++++++ .../core/rest/api/Feature.java | 58 ++++ .../core/rest/api/FeatureSet.java | 124 +++++++++ .../serverextension/core/rest/api/Field.java | 77 ++++++ .../core/rest/api/FieldType.java | 24 ++ .../QueryMapServiceLayerOperationInput.java | 9 + .../core/rest/api/RangeValue.java | 6 + .../core/rest/api/RenderingRule.java | 6 + .../core/rest/api/Transformation.java | 6 + .../internal/DelegateMethodInvoker.java | 2 +- .../core/util/GenericEsriEnum.java | 61 +++++ .../serverextension/core/util/StopWatch.java | 8 +- 36 files changed, 1518 insertions(+), 447 deletions(-) create mode 100644 examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/ClusterAssemblerCallbackHandler.java create mode 100644 examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/ClusterLayerResource.java rename examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/{LayersResource.java => ClusterLayersResource.java} (98%) create mode 100644 examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/ClusterPoint.java create mode 100644 examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/ClusterQueryOperationInput.java delete mode 100644 examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/Extent.java delete mode 100644 examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/LayerResource.java delete mode 100644 examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/Point.java delete mode 100644 examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/QueryOperationInput.java create mode 100644 server-extension-core/src/main/java/com/esri/serverextension/core/geodatabase/GeodatabaseCursorExtractor.java create mode 100644 server-extension-core/src/main/java/com/esri/serverextension/core/geodatabase/GeodatabaseObjectCallbackHandler.java create mode 100644 server-extension-core/src/main/java/com/esri/serverextension/core/geodatabase/GeodatabaseObjectCallbackHandlerCursorExtractor.java rename examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/Feature.java => server-extension-core/src/main/java/com/esri/serverextension/core/geodatabase/GeodatabaseObjectMapper.java (53%) create mode 100644 server-extension-core/src/main/java/com/esri/serverextension/core/geodatabase/GeodatabaseObjectMapperCursorExtractor.java create mode 100644 server-extension-core/src/main/java/com/esri/serverextension/core/geodatabase/GeodatabaseSystemException.java create mode 100644 server-extension-core/src/main/java/com/esri/serverextension/core/geodatabase/GeodatabaseTemplate.java create mode 100644 server-extension-core/src/main/java/com/esri/serverextension/core/rest/api/Extent.java create mode 100644 server-extension-core/src/main/java/com/esri/serverextension/core/rest/api/Feature.java create mode 100644 server-extension-core/src/main/java/com/esri/serverextension/core/rest/api/FeatureSet.java create mode 100644 server-extension-core/src/main/java/com/esri/serverextension/core/rest/api/Field.java create mode 100644 server-extension-core/src/main/java/com/esri/serverextension/core/rest/api/FieldType.java create mode 100644 server-extension-core/src/main/java/com/esri/serverextension/core/util/GenericEsriEnum.java diff --git a/examples/attribute-security-filter-soi/attribute-security-filter-soi.iml b/examples/attribute-security-filter-soi/attribute-security-filter-soi.iml index 82aab0b..223af81 100644 --- a/examples/attribute-security-filter-soi/attribute-security-filter-soi.iml +++ b/examples/attribute-security-filter-soi/attribute-security-filter-soi.iml @@ -3,8 +3,9 @@ - - file://$MODULE_DIR$/src/main/java/com/esri/arcgis/soi/attributesecurityfilter/AttributeSecurityFilterConfig.java + + file://$MODULE_DIR$/src/main/java/com/esri/serverextension/attributesecurityfilter/AttributeSecurityFilterConfig.java + file://$MODULE_DIR$/../../server-extension-core/src/main/java/com/esri/serverextension/core/server/DefaultConfig.java @@ -29,16 +30,18 @@ + + - - - - - - + + + + + + diff --git a/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/AboutResource.java b/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/AboutResource.java index 7e3a0a1..880ec16 100644 --- a/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/AboutResource.java +++ b/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/AboutResource.java @@ -14,12 +14,11 @@ package com.esri.serverextension.cluster; -import javax.annotation.Resource; - +import com.esri.arcgis.server.json.JSONObject; import org.springframework.stereotype.Service; import org.springframework.web.bind.annotation.RequestMapping; -import com.esri.arcgis.server.json.JSONObject; +import javax.annotation.Resource; @Service public class AboutResource { diff --git a/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/Cluster.java b/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/Cluster.java index 1a71e80..3e2060f 100644 --- a/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/Cluster.java +++ b/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/Cluster.java @@ -14,83 +14,72 @@ package com.esri.serverextension.cluster; +import com.esri.serverextension.core.rest.api.Feature; + import java.util.ArrayList; +import java.util.List; /** * Created by kcoffin on 2/8/17. */ public class Cluster { - private double _clusterCount; - private Point _point; - private ArrayList _features; + private double value; + private ClusterPoint point; + private List features; //A cluster needs to be created with at least one feature - public Cluster(Feature feature) { - _features = new ArrayList<>(); - _point = new Point(feature.getPoint()); - _clusterCount = feature.getValue(); - _features.add(feature); + public Cluster(Feature feature, double featureValue) { + features = new ArrayList<>(); + point = new ClusterPoint(feature.getGeometry()); + value = featureValue; + features.add(feature); } - public Point getPoint() { - return _point; + public ClusterPoint getPoint() { + return point; } - public void setClusterCount(double ct) { - _clusterCount = ct; + public double getValue() { + return value; } - public void addFeature(Feature feature) { - double value = feature.getValue(); - double count = _clusterCount; - _features.add(feature); - double ptc = value / (count + value); - double ctc = count / (count + value); - Point cluster = _point; - Point p = feature.getPoint(); + public void setValue(double ct) { + value = ct; + } + + public void addFeature(Feature feature, double featureValue) { + features.add(feature); + double ptc = featureValue / (value + featureValue); + double ctc = value / (value + featureValue); + ClusterPoint cluster = point; + ClusterPoint p = new ClusterPoint(feature.getGeometry()); double x = (p.x * ptc + (cluster.x * ctc)); double y = (p.y * ptc + (cluster.y * ctc)); cluster.x = x; cluster.y = y; - _clusterCount += value; + this.value += featureValue; } - public void addPointCluster(Feature feature, double ptCount) { - double count, x, y; - count = getValue(); + public void addPointCluster(Feature feature, double featureValue) { + double x, y; - Point p = feature.getPoint(); + ClusterPoint p = new ClusterPoint(feature.getGeometry()); getFeatures().add(feature); - double ptc = ptCount / (count + ptCount); - double ctc = count / (count + ptCount); - - x = (p.x * ptc + (_point.x * ctc)); - y = (p.y * ptc + (_point.y * ctc)); - _clusterCount += ptCount; - _point.x = x; - _point.y = y; - } + double ptc = featureValue / (value + featureValue); + double ctc = value / (value + featureValue); - public double getValue() { - return _clusterCount; + x = (p.x * ptc + (point.x * ctc)); + y = (p.y * ptc + (point.y * ctc)); + value += featureValue; + point.x = x; + point.y = y; } - public ArrayList getFeatures() { - return _features; - } - - - public void print() { - System.out.print("Cluster value:" + _clusterCount + " "); - _point.print(); - System.out.println(); - for (Feature f : _features) { - f.print(); - } - System.out.println("=============="); + public List getFeatures() { + return features; } } diff --git a/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/ClusterAssembler.java b/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/ClusterAssembler.java index ad6ec95..5945a83 100644 --- a/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/ClusterAssembler.java +++ b/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/ClusterAssembler.java @@ -14,130 +14,100 @@ package com.esri.serverextension.cluster; +import com.esri.serverextension.core.rest.api.Extent; +import com.esri.serverextension.core.rest.api.Feature; + import java.util.ArrayList; -import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Map; /** - * Assembles the cluster + * Assembles the cluster. + * + * Created by kcoffin on 2/8/17. + * */ public class ClusterAssembler { //The grid's extent - private Extent _extent; - + private Extent bbox; //cellsize of grid - private double _cellSize; + private double cellSize; - private double _mapUnitsPerPixel; + private double mapUnitsPerPixel; //number of columns in grid - private int _numColumns; + private int numColumns; //number of columns in grid - private int _numRows; - + private int numRows; //the cells which keeps the cluster info - private Map> _cells; + private Map> cells; //the desired cluster distance in pixels - private double _clusterDistanceInPixels; + private double clusterDistanceInPixels; //All the clusters which are created - private ArrayList _clusters; + private List clusters; + // The field to determine a clusters weight + private String clusterFieldName; /** - * Assemble the cluster + * Creates a new cluster assembler. * - * @param features all the features * @param mapUnitsPerPixel map units per pixel (like meters per pixel) * @param clusterDistanceInPixels cluster distance in pixels - * @param extent the extent in real world coordinates + * @param bbox the extent in real world coordinates + * @param clusterFieldName the field to determine a clusters weight */ - public ClusterAssembler(ArrayList features, double mapUnitsPerPixel, - double clusterDistanceInPixels, Extent extent) { - addAllFeatures(features, mapUnitsPerPixel, clusterDistanceInPixels, extent); + public ClusterAssembler(double mapUnitsPerPixel, + double clusterDistanceInPixels, + Extent bbox, + String clusterFieldName) { + cells = new HashMap<>(); + this.mapUnitsPerPixel = mapUnitsPerPixel; + this.clusterDistanceInPixels = clusterDistanceInPixels; + cellSize = mapUnitsPerPixel * this.clusterDistanceInPixels; + this.bbox = bbox; + numColumns = getGridColumn(bbox.getWidth()) + 1; + numRows = getGridRow(bbox.getHeight()) + 1; + clusters = new ArrayList<>(); + this.clusterFieldName = clusterFieldName; } /** - * Retrieves all the clusters + * Returns the assembled clusters. * - * @return + * @return the assembled clusters */ - public ArrayList getClusters() { - return _clusters; + public List getClusters() { + return clusters; } - - //Adds all the features. Called internally by the ctor - private void addAllFeatures(ArrayList features, double mapUnitsPerPixel, - double clusterDistanceInPixels, Extent extent - ) { - _cells = new HashMap<>(); - _mapUnitsPerPixel = mapUnitsPerPixel; - _clusterDistanceInPixels = clusterDistanceInPixels; - _cellSize = mapUnitsPerPixel * _clusterDistanceInPixels; - _extent = extent; - _numColumns = getGridColumn(extent.getWidth()) + 1; - _numRows = getGridRow(extent.getHeight()) + 1; - _clusters = new ArrayList<>(); - - //first sort the features based on the clusterfieldIndex - // Sorting by Lambda - Collections.sort(features, (Feature feature2, Feature feature1) -> - ((Double) feature1.getValue()).compareTo(feature2.getValue())); - - - /* JAVA 1.7 - Collections.sort(features, new Comparator() { - @Override - public int compare(Feature feature2, Feature feature1) - { - return ((Double)feature1.getValue()).compareTo((Double)feature2.getValue()); - } - }); - */ - - for (Feature feature : features) { - addFeature(feature); - } - - - for (Cluster cluster : _clusters) { - fixCluster(cluster); - } - - - for (Cluster cluster : _clusters) { - fixCluster(cluster); - } - -/* - System.out.println("+++++++++++++++++++++++++++++++++++++"); - for (Cluster cluster:_clusters){ - examineCluster(cluster); - } -*/ -/* - for (Cluster cluster:_clusters){ - cluster.print(); - } -*/ - + /** + * Builds the clusters. Call this method after all features have been added and before accessing the + * assembled clusters. + */ + public void buildClusters() { +// for (Cluster cluster : clusters) { +// fixCluster(cluster); +// } +// for (Cluster cluster : clusters) { +// fixCluster(cluster); +// } } - /** - * Add a feature + * Adds a feature. * * @param feature */ - private void addFeature(Feature feature) { - Cluster closestCluster = getClosestCluster(feature.getPoint()); + public void addFeature(Feature feature) { + Cluster closestCluster = getClosestCluster(new ClusterPoint(feature.getGeometry())); if (closestCluster != null) { addFeatureToCluster(feature, closestCluster); @@ -149,12 +119,12 @@ private void addFeature(Feature feature) { //from yValue real-world, what is the grid row private int getGridRow(double yValue) { - return (int) Math.floor((yValue - _extent.getYMin()) / _cellSize); + return (int) Math.floor((yValue - bbox.getYmin()) / cellSize); } //from xValue real-world, what is the grid column private int getGridColumn(double xValue) { - return (int) Math.floor((xValue - _extent.getXMin()) / _cellSize); + return (int) Math.floor((xValue - bbox.getXmin()) / cellSize); } //add a feature to an EXISTING cluster @@ -163,7 +133,7 @@ private void addFeatureToCluster(Feature feature, Cluster cluster) { removeClusterFromGrid(cluster); //add the feature to the cluster - cluster.addFeature(feature); + cluster.addFeature(feature, getFeatureValue(feature)); //add it back in to the grid addClusterToGrid(cluster); @@ -171,11 +141,11 @@ private void addFeatureToCluster(Feature feature, Cluster cluster) { //remove a cluster from the grid (it is already in the grid) private void removeClusterFromGrid(Cluster cluster) { - Point pt = cluster.getPoint(); + ClusterPoint pt = cluster.getPoint(); int row = getGridRow(pt.y); int column = getGridColumn(pt.x); - int index = _numRows * row + column; - ArrayList cell = _cells.get(index); + int index = numRows * row + column; + ArrayList cell = cells.get(index); if (cell != null) { cell.remove(cluster); @@ -186,23 +156,23 @@ private void removeClusterFromGrid(Cluster cluster) { //Add the cluster to the grid private void addClusterToGrid(Cluster cluster) { - Point pt = cluster.getPoint(); + ClusterPoint pt = cluster.getPoint(); int row = getGridRow(pt.y); int column = getGridColumn(pt.x); - int index = _numRows * row + column; - ArrayList cell = _cells.get(index); + int index = numRows * row + column; + ArrayList cell = cells.get(index); if (cell == null) { cell = new ArrayList<>(); - _cells.put(index, cell); + cells.put(index, cell); } cell.add(cluster); } private void createCluster(Feature feature) { - Cluster cluster = new Cluster(feature); + Cluster cluster = new Cluster(feature, getFeatureValue(feature)); addClusterToGrid(cluster); - _clusters.add(cluster); + clusters.add(cluster); } @@ -212,12 +182,12 @@ private void createCluster(Feature feature) { * @param pt * @return */ - public Cluster getClosestCluster(Point pt) { + private Cluster getClosestCluster(ClusterPoint pt) { int row = getGridRow(pt.y); int column = getGridColumn(pt.x); //should never happen as all features should come from within the extent - if (row < 0 || column < 0 || row >= _numRows || column >= _numColumns) { + if (row < 0 || column < 0 || row >= numRows || column >= numColumns) { System.out.println("There's an error in the query"); return null; } @@ -227,7 +197,7 @@ public Cluster getClosestCluster(Point pt) { if (row > 0) { yStart = row - 1; } - if (row < _numRows - 1) { + if (row < numRows - 1) { yEnd = row + 1; } @@ -237,15 +207,15 @@ public Cluster getClosestCluster(Point pt) { if (column > 0) { xStart = column - 1; } - if (column < _numColumns - 1) { + if (column < numColumns - 1) { xEnd = column + 1; } /* - int xStart = (int)(Math.floor((extent.xmin - this._xmin) / this._cellSize)); - int xEnd = (int)(Math.floor((extent.xmax - this._xmin) / this._cellSize)); - int yStart = (int)(Math.floor((extent.ymin - this._ymin) / this._cellSize)); - int yEnd = (int)(Math.floor((extent.ymax - this._ymin) / this._cellSize)); + int xStart = (int)(Math.floor((extent.xmin - this._xmin) / this.cellSize)); + int xEnd = (int)(Math.floor((extent.xmax - this._xmin) / this.cellSize)); + int yStart = (int)(Math.floor((extent.ymin - this._ymin) / this.cellSize)); + int yEnd = (int)(Math.floor((extent.ymax - this._ymin) / this.cellSize)); */ Cluster minCluster = null; @@ -253,8 +223,8 @@ public Cluster getClosestCluster(Point pt) { for (int x = xStart; x <= xEnd; x++) { for (int y = yStart; y <= yEnd; y++) { - int index = _numRows * y + x; - ArrayList cell = _cells.get(index); + int index = numRows * y + x; + ArrayList cell = cells.get(index); if (cell != null) { for (int i = 0; i < cell.size(); i++) { Cluster cluster = cell.get(i); @@ -270,7 +240,7 @@ public Cluster getClosestCluster(Point pt) { if (minDis2 > 0) { minDis2 = Math.sqrt(minDis2); } - if (minDis2 > _cellSize) { + if (minDis2 > cellSize) { return null; } return minCluster; @@ -278,21 +248,19 @@ public Cluster getClosestCluster(Point pt) { //This examines a cluster and determines if all features in it are the closest to it. - public void examineCluster(Cluster cluster) { - - - ArrayList features = cluster.getFeatures(); + private void examineCluster(Cluster cluster) { + List features = cluster.getFeatures(); for (int k = 0; k < features.size(); k++) { Feature feature = features.get(k); - Point p = feature.getPoint(); + ClusterPoint p = new ClusterPoint(feature.getGeometry()); Cluster clust = getClosestCluster(p); if (clust != cluster) { - double pixels = cluster.getPoint().distance(p) / _mapUnitsPerPixel; + double pixels = cluster.getPoint().distance(p) / mapUnitsPerPixel; System.out.print("Not closest to cluster...pixel Distance=" + Math.round(pixels)); if (clust == null) { System.out.println(); } else { - double closerPixels = clust.getPoint().distance(p) / _mapUnitsPerPixel; + double closerPixels = clust.getPoint().distance(p) / mapUnitsPerPixel; System.out.println(" Closer to distance=" + Math.round(closerPixels)); } } @@ -300,32 +268,31 @@ public void examineCluster(Cluster cluster) { } - public void fixCluster(Cluster cluster) { - double distance = _cellSize;//this._mapUnitsPerPixel*this._clusterDistanceInPixels; + private void fixCluster(Cluster cluster) { + double distance = cellSize;//this.mapUnitsPerPixel*this.clusterDistanceInPixels; double distanceSq = distance * distance; double test = distanceSq * 2.0; - ArrayList features = cluster.getFeatures(); + List features = cluster.getFeatures(); for (int k = features.size() - 1; k >= 0; k--) { Feature feature = features.get(k); - Point p = feature.getPoint(); + ClusterPoint p = new ClusterPoint(feature.getGeometry()); Cluster minPt = this.getClosestCluster(p); if (minPt != null && minPt != cluster) { - double ptCount = feature.getValue(); + double featureValue = getFeatureValue(feature); - double count = cluster.getValue(); + double clusterValue = cluster.getValue(); //features.splice(k, 1); features.remove(k); - double ptCountCount = count - ptCount; - cluster.setClusterCount(ptCountCount); - Point clusterPoint = cluster.getPoint(); - if (ptCountCount > 0) { - double ptc = ptCount / ptCountCount; - double ctc = count / ptCountCount; - + double newClusterValue = clusterValue - featureValue; + cluster.setValue(newClusterValue); + ClusterPoint clusterPoint = cluster.getPoint(); + if (newClusterValue > 0) { + double ptc = featureValue / newClusterValue; + double ctc = clusterValue / newClusterValue; double xx = (clusterPoint.x * ctc) - (p.x * ptc);//x = (cluster.x*count-pt.x*ptCount)/(count-ptCount) double yy = (clusterPoint.y * ctc) - (p.y * ptc); @@ -339,31 +306,24 @@ public void fixCluster(Cluster cluster) { clusterPoint.x = 0; clusterPoint.y = 0; } - - //add to another //this._clusterAddPoint(feature, minPt, ptCount); - minPt.addPointCluster(feature, ptCount); - - + minPt.addPointCluster(feature, featureValue); } - } - - } - public void addCellsForAllClusters() { - //this._cells = []; - _cells = new HashMap<>(); - for (Cluster cluster : _clusters) { + private void addCellsForAllClusters() { + //this.cells = []; + cells = new HashMap<>(); + for (Cluster cluster : clusters) { addCells(cluster); } } - public void addCells(Cluster cluster) { - Point clusterPoint = cluster.getPoint(); + private void addCells(Cluster cluster) { + ClusterPoint clusterPoint = cluster.getPoint(); int row = getGridRow(clusterPoint.y); int column = getGridColumn(clusterPoint.x); @@ -372,7 +332,7 @@ public void addCells(Cluster cluster) { if (row > 0) { yStart = row - 1; } - if (row < _numRows - 1) { + if (row < numRows - 1) { yEnd = row + 1; } @@ -382,17 +342,17 @@ public void addCells(Cluster cluster) { if (column > 0) { xStart = column - 1; } - if (column < _numColumns - 1) { + if (column < numColumns - 1) { xEnd = column + 1; } for (int i = xStart; i <= xEnd; i++) { for (int j = yStart; j <= yEnd; j++) { - int index = this._numRows * j + i; - ArrayList cell = _cells.get(index); + int index = this.numRows * j + i; + ArrayList cell = cells.get(index); if (cell == null) { cell = new ArrayList<>(); - _cells.put(index, cell); + cells.put(index, cell); } cell.add(cluster); } @@ -401,5 +361,16 @@ public void addCells(Cluster cluster) { } + private double getFeatureValue(Feature feature) { + Object value = feature.getAttributes().get(clusterFieldName); + if (value == null) { + return 0.0d; + } + if (value instanceof Number) { + return ((Number)feature.getAttributes().get(clusterFieldName)).doubleValue(); + } + throw new IllegalArgumentException("Cluster field type must be numeric."); + } + } diff --git a/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/ClusterAssemblerCallbackHandler.java b/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/ClusterAssemblerCallbackHandler.java new file mode 100644 index 0000000..1e40604 --- /dev/null +++ b/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/ClusterAssemblerCallbackHandler.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2017 Esri + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License.​ + */ + +package com.esri.serverextension.cluster; + +import com.esri.arcgis.geodatabase.IFeature; +import com.esri.arcgis.geodatabase.IField; +import com.esri.arcgis.geodatabase.IRow; +import com.esri.serverextension.core.geodatabase.GeodatabaseObjectCallbackHandler; +import com.esri.serverextension.core.rest.api.Feature; + +import java.io.IOException; +import java.util.LinkedHashMap; +import java.util.Map; + +public class ClusterAssemblerCallbackHandler implements GeodatabaseObjectCallbackHandler { + + private IField[] fields; + private ClusterAssembler clusterAssembler; + private int featureCount = 0; + + public ClusterAssemblerCallbackHandler(ClusterAssembler clusterAssembler) { + this.clusterAssembler = clusterAssembler; + } + + public int getFeatureCount() { + return featureCount; + } + + @Override + public void setFields(IField[] fields) throws IOException { + this.fields = fields; + } + + @Override + public void processRow(IRow row) throws IOException { + throw new UnsupportedOperationException("This class only handles features."); + } + + @Override + public void processFeature(IFeature feature) throws IOException { + featureCount++; + Map attributes = new LinkedHashMap<>(); + for (int i = 0; i < fields.length; i++) { + attributes.put(fields[i].getName(), feature.getValue(i)); + } + Feature f = new Feature(); + f.setGeometry(feature.getShape()); + f.setAttributes(attributes); + clusterAssembler.addFeature(f); + } +} diff --git a/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/ClusterLayerResource.java b/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/ClusterLayerResource.java new file mode 100644 index 0000000..b22851d --- /dev/null +++ b/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/ClusterLayerResource.java @@ -0,0 +1,165 @@ +/* + * Copyright (c) 2017 Esri + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License.​ + */ + +package com.esri.serverextension.cluster; + +import com.esri.arcgis.carto.IMapLayerInfo; +import com.esri.arcgis.geodatabase.*; +import com.esri.arcgis.geometry.IPoint; +import com.esri.arcgis.geometry.ISpatialReference; +import com.esri.arcgis.geometry.Point; +import com.esri.arcgis.interop.Cleaner; +import com.esri.arcgis.server.json.JSONObject; +import com.esri.serverextension.core.geodatabase.GeodatabaseTemplate; +import com.esri.serverextension.core.rest.api.Feature; +import com.esri.serverextension.core.rest.api.FeatureSet; +import com.esri.serverextension.core.rest.api.Field; +import com.esri.serverextension.core.rest.api.FieldType; +import com.esri.serverextension.core.server.ServerObjectExtensionContext; +import com.esri.serverextension.core.util.ArcObjectsInteropException; +import com.esri.serverextension.core.util.GenericEsriEnum; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Service; +import org.springframework.util.CollectionUtils; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; + +import javax.ws.rs.BeanParam; +import java.io.IOException; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +@Service +public class ClusterLayerResource { + + @RequestMapping("/layers/{layerId}") + public JSONObject getLayerResource(@PathVariable("layerId") int layerId, ServerObjectExtensionContext serverContext) { + IMapLayerInfo layerInfo = MapServerUtilities.getPointFeatureLayerByID(layerId, serverContext); + JSONObject layerObject = new JSONObject(); + try { + layerObject.put("name", layerInfo.getName()); + layerObject.put("id", layerInfo.getID()); + layerObject.put("description", layerInfo.getDescription()); + } catch (IOException ex) { + throw new ArcObjectsInteropException( + String.format("Failed to get details for layer: %1$d", layerId)); + } + return layerObject; + } + + @RequestMapping("/layers/{layerId}/query") + public FeatureSet query(@PathVariable("layerId") int layerId, @BeanParam ClusterQueryOperationInput input, ServerObjectExtensionContext serverContext) { + try { + IFeatureClass featureClass = MapServerUtilities.getPointFeatureClassByLayerID(layerId, serverContext); + IQueryFilter queryFilter = getQueryFilter(input, featureClass.getShapeFieldName()); + GeodatabaseTemplate geodatabaseTemplate = new GeodatabaseTemplate(); + ClusterAssembler clusterAssembler = new ClusterAssembler( + input.getMapUnitsPerPixel(), + input.getClusterDistanceInPixels(), + input.getBbox(), + input.getClusterField() + ); + ClusterAssemblerCallbackHandler clusterAssemblerCallbackHandler = new ClusterAssemblerCallbackHandler(clusterAssembler); + geodatabaseTemplate.query(featureClass, queryFilter, clusterAssemblerCallbackHandler); + FeatureSet featureSet = new FeatureSet(); + featureSet.setDisplayFieldName(input.getClusterField()); + Field field = new Field(input.getClusterField(), + FieldType.esriFieldTypeDouble, input.getClusterField()); + List fields = new ArrayList<>(); + fields.add(field); + featureSet.setFields(fields); + featureSet.setSpatialReference(getOutSpatialReference(input, serverContext)); + clusterAssembler.buildClusters(); + List clusters = clusterAssembler.getClusters(); + if (!CollectionUtils.isEmpty(clusters)) { + List features = new ArrayList<>(clusterAssembler.getClusters().size()); + for (Cluster cluster : clusterAssembler.getClusters()) { + Feature clusterFeature = new Feature(); + ClusterPoint clusterPoint = cluster.getPoint(); + IPoint point = new Point(); + point.setX(clusterPoint.x); + point.setY(clusterPoint.y); + clusterFeature.setGeometry(point); + Map attributes = new LinkedHashMap<>(); + attributes.put(input.getClusterField().intern(), cluster.getValue()); + clusterFeature.setAttributes(attributes); + features.add(clusterFeature); + } + featureSet.setFeatures(features); + } else { + List features = new ArrayList<>(1); + Feature clusterFeature = new Feature(); + Map attributes = new LinkedHashMap<>(); + attributes.put(input.getClusterField().intern(), clusterAssemblerCallbackHandler.getFeatureCount()); + clusterFeature.setAttributes(attributes); + features.add(clusterFeature); + featureSet.setFeatures(features); + } + return featureSet; + } catch (IOException ex) { + throw new ArcObjectsInteropException( + String.format("Failed to query cluster layer: %1$d", layerId)); + } + } + + private IQueryFilter getQueryFilter(ClusterQueryOperationInput input, String shapeFieldName) { + try { + IQueryFilter queryFilter = null; + if (input.getGeometry() != null) { + ISpatialFilter spatialFilter = new SpatialFilter(); + spatialFilter.setGeometryByRef(input.getGeometry()); + spatialFilter.setGeometryField(shapeFieldName); + if (input.getSpatialRel() != null) { + spatialFilter.setSpatialRel(GenericEsriEnum.valueOf(esriSpatialRelEnum.class, input.getSpatialRel().name())); + } + if (StringUtils.isNotEmpty(input.getRelationParam())) { + spatialFilter.setSpatialRelDescription(input.getRelationParam()); + } + if (input.getOutSR() != null) { + spatialFilter.setOutputSpatialReferenceByRef(shapeFieldName, input.getOutSR()); + } + queryFilter = spatialFilter; + } else { + queryFilter = new QueryFilter(); + } + if (StringUtils.isNotEmpty(input.getWhere())) { + SQLCheck sqlCheck = new SQLCheck(); + sqlCheck.checkWhereClause(input.getWhere()); + Cleaner.release(sqlCheck); + queryFilter.setWhereClause(input.getWhere()); + } + if (StringUtils.isNotEmpty(input.getOrderByFields())) { + ((IQueryFilterDefinition)queryFilter).setPostfixClause(String.format( + "ORDER BY %1$s", input.getOrderByFields() + )); + } + if (StringUtils.isNotEmpty(input.getClusterField())) { + queryFilter.setSubFields(input.getClusterField()); + queryFilter.addField(shapeFieldName); + } + return queryFilter; + } catch (IOException ex) { + throw new ArcObjectsInteropException("Failed to create query filter.", ex); + } + } + + private ISpatialReference getOutSpatialReference(ClusterQueryOperationInput input, ServerObjectExtensionContext serverContext) { + if (input.getOutSR() != null) { + return input.getOutSR(); + } + return MapServerUtilities.getMapSpatialReference(serverContext); + } +} diff --git a/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/LayersResource.java b/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/ClusterLayersResource.java similarity index 98% rename from examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/LayersResource.java rename to examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/ClusterLayersResource.java index c99568a..98b3264 100644 --- a/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/LayersResource.java +++ b/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/ClusterLayersResource.java @@ -26,7 +26,7 @@ import java.util.List; @Service -public class LayersResource { +public class ClusterLayersResource { @RequestMapping("/layers") public JSONObject getLayersResource(ServerObjectExtensionContext serverContext) { diff --git a/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/ClusterPoint.java b/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/ClusterPoint.java new file mode 100644 index 0000000..7a663b5 --- /dev/null +++ b/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/ClusterPoint.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2017 Esri + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License.​ + */ + +package com.esri.serverextension.cluster; + +import com.esri.arcgis.geometry.IGeometry; +import com.esri.arcgis.geometry.IPoint; +import com.esri.arcgis.geometry.Point; +import com.esri.serverextension.core.util.ArcObjectsInteropException; + +import java.io.IOException; + +/** + * Created by kcoffin on 2/8/17. + */ +public class ClusterPoint { + public double x; + public double y; + + public ClusterPoint(double x, double y) { + this.x = x; + this.y = y; + } + + public ClusterPoint(ClusterPoint pt) { + x = pt.x; + y = pt.y; + } + + public ClusterPoint(IGeometry geometry) { + if (geometry == null) { + throw new NullPointerException("Argument 'geometry' must not be null."); + } + if (geometry instanceof IPoint) { + IPoint pt = (IPoint)geometry; + try { + x = pt.getX(); + y = pt.getY(); + } catch (IOException ex) { + throw new ArcObjectsInteropException("Failed to create cluster point from IPoint."); + } + } else { + throw new IllegalArgumentException(String.format("Geometry is not a point: %1$s", geometry.getClass().getName())); + } + } + + public double squareDistance(ClusterPoint pt) { + double dx = pt.x - x; + double dy = pt.y - y; + return (dx * dx) + (dy * dy); + } + + public double distance(ClusterPoint pt) { + return Math.sqrt(squareDistance(pt)); + } + + public IPoint getPoint() { + try { + Point pt = new Point(); + pt.setX(x); + pt.setY(y); + return pt; + } catch (IOException ex) { + throw new ArcObjectsInteropException("Failed to create point.", ex); + } + } +} diff --git a/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/ClusterQueryOperationInput.java b/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/ClusterQueryOperationInput.java new file mode 100644 index 0000000..84c0f8f --- /dev/null +++ b/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/ClusterQueryOperationInput.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2017 Esri + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License.​ + */ + +package com.esri.serverextension.cluster; + +import com.esri.serverextension.core.rest.api.Extent; +import com.esri.serverextension.core.rest.api.QueryMapServiceLayerOperationInput; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +import javax.xml.bind.annotation.XmlRootElement; + +@XmlRootElement +@JsonIgnoreProperties(ignoreUnknown = true) +public class ClusterQueryOperationInput extends QueryMapServiceLayerOperationInput { + + private static final long serialVersionUID = 1L; + + private Extent bbox; + private Double mapUnitsPerPixel; + private Integer clusterDistanceInPixels; + private String clusterField; + + public ClusterQueryOperationInput() { + } + + public Extent getBbox() { + return bbox; + } + + public void setBbox(Extent bbox) { + this.bbox = bbox; + } + + public Double getMapUnitsPerPixel() { + return mapUnitsPerPixel; + } + + public void setMapUnitsPerPixel(Double mapUnitsPerPixel) { + this.mapUnitsPerPixel = mapUnitsPerPixel; + } + + public Integer getClusterDistanceInPixels() { + return clusterDistanceInPixels; + } + + public void setClusterDistanceInPixels(Integer clusterDistanceInPixels) { + this.clusterDistanceInPixels = clusterDistanceInPixels; + } + + public String getClusterField() { + return clusterField; + } + + public void setClusterField(String clusterField) { + this.clusterField = clusterField; + } +} diff --git a/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/ClusteringExtension.java b/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/ClusteringExtension.java index b3fba71..6640dba 100644 --- a/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/ClusteringExtension.java +++ b/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/ClusteringExtension.java @@ -14,10 +14,6 @@ package com.esri.serverextension.cluster; -import com.esri.arcgis.datasourcesGDB.SqlWorkspace; -import com.esri.arcgis.geodatabase.ISqlWorkspace; -import com.esri.arcgis.geodatabase.IWorkspace; -import com.esri.arcgis.geodatabase.esriDatasetType; import com.esri.arcgis.interop.AutomationException; import com.esri.arcgis.interop.extn.ArcGISExtension; import com.esri.arcgis.interop.extn.ServerObjectExtProperties; @@ -25,12 +21,6 @@ import com.esri.arcgis.server.json.JSONObject; import com.esri.arcgis.system.ServerUtilities; import com.esri.serverextension.core.server.AbstractRestServerObjectExtension; -import com.esri.serverextension.core.server.AbstractRestServerObjectInterceptor; -import com.esri.serverextension.core.server.MapServerUtilities; -import com.esri.serverextension.core.server.ServerObjectExtensionException; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import java.io.IOException; @@ -42,10 +32,10 @@ servicetype = "MapService") public class ClusteringExtension extends AbstractRestServerObjectExtension { - public static final String QUERY_OPERATION_PARAMETER_NAMES = "text, geometry, " - + "geometryType, inSR, spatialRel, relationParam, where, objectIds" - + "distance, units, outFields, outSR, orderByFields, outStatistics" - + "intersectGeometryDistance, intersectGeometryUnits, intersectLayers, datumTransformation"; + public static final String QUERY_OPERATION_PARAMETER_NAMES = "bbox, " + + "mapUnitsPerPixel, clusterDistanceInPixels, clusterField, " + + "geometry, geometryType, inSR, spatialRel, relationParam, where, " + + "outField, outSR, orderByFields"; @Override protected void doConfigure( diff --git a/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/Extent.java b/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/Extent.java deleted file mode 100644 index c141d4f..0000000 --- a/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/Extent.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright (c) 2017 Esri - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0 - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License.​ - */ - -package com.esri.serverextension.cluster; - -/** - * Immutable Extent - */ -public class Extent { - private double _xmin; - private double _ymin; - private double _xmax; - private double _ymax; - - /** - * Construct an extent. This is where everything gets assigned - * - * @param xmin - * @param ymin - * @param xmax - * @param ymax - */ - public Extent(double xmin, double ymin, double xmax, double ymax) { - _xmin = xmin; - _ymin = ymin; - _xmax = xmax; - _ymax = ymax; - } - - public double getWidth() { - return _xmax - _xmin; - } - - public double getHeight() { - return _ymax - _ymin; - } - - public double getXMin() { - return _xmin; - } - - public double getYMin() { - return _ymin; - } - - public double getXMax() { - return _xmax; - } - - public double getYMax() { - return _ymax; - } -} diff --git a/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/LayerResource.java b/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/LayerResource.java deleted file mode 100644 index 8018d5c..0000000 --- a/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/LayerResource.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (c) 2017 Esri - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0 - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License.​ - */ - -package com.esri.serverextension.cluster; - -import com.esri.arcgis.carto.IMapLayerInfo; -import com.esri.arcgis.server.json.JSONObject; -import com.esri.serverextension.core.server.ServerObjectExtensionContext; -import com.esri.serverextension.core.util.ArcObjectsInteropException; -import org.springframework.stereotype.Service; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestMapping; - -import java.io.IOException; - -@Service -public class LayerResource { - - @RequestMapping("/layers/{layerId}") - public JSONObject getLayerResource(@PathVariable("layerId") int layerId, ServerObjectExtensionContext serverContext) { - IMapLayerInfo layerInfo = MapServerUtilities.getPointFeatureLayerByID(layerId, serverContext); - JSONObject layerObject = new JSONObject(); - try { - layerObject.put("name", layerInfo.getName()); - layerObject.put("id", layerInfo.getID()); - layerObject.put("description", layerInfo.getDescription()); - } catch (IOException ex) { - throw new ArcObjectsInteropException( - String.format("Failed to get details for layer: %1$d", layerId)); - } - return layerObject; - } -} diff --git a/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/MapServerUtilities.java b/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/MapServerUtilities.java index 48b643c..0a7cad0 100644 --- a/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/MapServerUtilities.java +++ b/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/MapServerUtilities.java @@ -17,6 +17,7 @@ import com.esri.arcgis.carto.*; import com.esri.arcgis.geodatabase.FeatureClass; import com.esri.arcgis.geodatabase.IFeatureClass; +import com.esri.arcgis.geometry.ISpatialReference; import com.esri.arcgis.geometry.esriGeometryType; import com.esri.arcgis.interop.Cleaner; import com.esri.serverextension.core.server.ServerObjectExtensionContext; @@ -78,4 +79,36 @@ public static final IMapLayerInfo getPointFeatureLayerByID(int id, } throw new IllegalArgumentException(String.format("No such point feature layer: %1$d", id)); } + + public static final IFeatureClass getPointFeatureClassByLayerID(int layerId, + ServerObjectExtensionContext serverContext) { + try { + IMapLayerInfo layerInfo = getPointFeatureLayerByID(layerId, serverContext); + IMapServerDataAccess mapServerDataAccess = (IMapServerDataAccess) serverContext + .getServerObject(); + IMapServer3 mapServer = (IMapServer3) mapServerDataAccess; + String mapName = mapServer.getDefaultMapName(); + Object dataSource = mapServerDataAccess + .getDisplayDataSource(mapName, layerInfo.getID()); + return new FeatureClass(dataSource); + } catch (IOException ex) { + throw new ArcObjectsInteropException( + "Failed to get point feature class from map server object."); + } + } + + public static final ISpatialReference getMapSpatialReference(ServerObjectExtensionContext serverContext) { + try { + IMapServer3 mapServer = (IMapServer3) serverContext + .getServerObject(); + String mapName = mapServer.getDefaultMapName(); + IMapServerInfo mapServerInfo = (IMapServerInfo) mapServer + .getServerInfo(mapName); + IMapDescription mapDescription = mapServerInfo.getDefaultMapDescription(); + return mapDescription.getSpatialReference(); + } catch (IOException ex) { + throw new ArcObjectsInteropException( + "Failed to get point feature class from map server object."); + } + } } diff --git a/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/Point.java b/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/Point.java deleted file mode 100644 index 46d516b..0000000 --- a/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/Point.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (c) 2017 Esri - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0 - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License.​ - */ - -package com.esri.serverextension.cluster; - -/** - * Created by kcoffin on 2/8/17. - */ -public class Point { - public double x; - public double y; - - public Point(double x, double y) { - this.x = x; - this.y = y; - } - - public Point(Point pt) { - x = pt.x; - y = pt.y; - } - - public double squareDistance(Point pt) { - double dx = pt.x - x; - double dy = pt.y - y; - return (dx * dx) + (dy * dy); - } - - public double distance(Point pt) { - return Math.sqrt(squareDistance(pt)); - } - - - public void print() { - System.out.print("(" + x + "," + y + ")"); - - } -} diff --git a/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/QueryOperationInput.java b/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/QueryOperationInput.java deleted file mode 100644 index 34b62a7..0000000 --- a/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/QueryOperationInput.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (c) 2017 Esri - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0 - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License.​ - */ - -package com.esri.serverextension.cluster; - -import javax.xml.bind.annotation.XmlRootElement; - -import com.esri.arcgis.geometry.IGeometry; -import com.esri.arcgis.geometry.ISpatialReference; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.esri.arcgis.geometry.esriGeometryType; -import com.esri.arcgis.geodatabase.esriSpatialRelEnum; - -@XmlRootElement -@JsonIgnoreProperties(ignoreUnknown = true) -public class QueryOperationInput { - - private static final long serialVersionUID = 1L; - - public QueryOperationInput() { - } - - private String text; - private IGeometry geometry; - private esriGeometryType geometryType; - private ISpatialReference inSR; - private esriSpatialRelEnum spatialRel; - private String relationParam; - - -} diff --git a/server-extension-core/pom.xml b/server-extension-core/pom.xml index c46d920..5853bab 100644 --- a/server-extension-core/pom.xml +++ b/server-extension-core/pom.xml @@ -67,6 +67,10 @@ + + org.springframework + spring-jdbc + javax.enterprise cdi-api @@ -75,17 +79,17 @@ com.fasterxml.jackson.core jackson-annotations - 2.8.7 + 2.6.7 com.fasterxml.jackson.jaxrs jackson-jaxrs-base - 2.8.7 + 2.6.7 com.fasterxml.jackson.jaxrs jackson-jaxrs-json-provider - 2.8.7 + 2.6.7 org.glassfish.jersey.core diff --git a/server-extension-core/src/main/java/com/esri/serverextension/core/geodatabase/GeodatabaseCursorExtractor.java b/server-extension-core/src/main/java/com/esri/serverextension/core/geodatabase/GeodatabaseCursorExtractor.java new file mode 100644 index 0000000..32668eb --- /dev/null +++ b/server-extension-core/src/main/java/com/esri/serverextension/core/geodatabase/GeodatabaseCursorExtractor.java @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2017 Esri + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License.​ + */ + +package com.esri.serverextension.core.geodatabase; + +import java.io.IOException; + +import com.esri.arcgis.geodatabase.ICursor; +import com.esri.arcgis.geodatabase.IFeatureCursor; +import com.esri.arcgis.geodatabase.IField; + +public interface GeodatabaseCursorExtractor { + + T extractData(ICursor cursor, IField[] fields) throws IOException; + + T extractData(IFeatureCursor featureCursor, IField[] fields) throws IOException; +} diff --git a/server-extension-core/src/main/java/com/esri/serverextension/core/geodatabase/GeodatabaseObjectCallbackHandler.java b/server-extension-core/src/main/java/com/esri/serverextension/core/geodatabase/GeodatabaseObjectCallbackHandler.java new file mode 100644 index 0000000..1e7e4b1 --- /dev/null +++ b/server-extension-core/src/main/java/com/esri/serverextension/core/geodatabase/GeodatabaseObjectCallbackHandler.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2017 Esri + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License.​ + */ + +package com.esri.serverextension.core.geodatabase; + +import com.esri.arcgis.geodatabase.IFeature; +import com.esri.arcgis.geodatabase.IField; +import com.esri.arcgis.geodatabase.IRow; + +import java.io.IOException; + +public interface GeodatabaseObjectCallbackHandler { + + public void setFields(IField[] fields) throws IOException; + + public void processRow(IRow row) throws IOException; + + public void processFeature(IFeature feature) throws IOException; +} diff --git a/server-extension-core/src/main/java/com/esri/serverextension/core/geodatabase/GeodatabaseObjectCallbackHandlerCursorExtractor.java b/server-extension-core/src/main/java/com/esri/serverextension/core/geodatabase/GeodatabaseObjectCallbackHandlerCursorExtractor.java new file mode 100644 index 0000000..26e55e6 --- /dev/null +++ b/server-extension-core/src/main/java/com/esri/serverextension/core/geodatabase/GeodatabaseObjectCallbackHandlerCursorExtractor.java @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2017 Esri + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License.​ + */ + +package com.esri.serverextension.core.geodatabase; + +import com.esri.arcgis.geodatabase.*; +import com.esri.arcgis.interop.AutomationException; +import com.esri.arcgis.system.Cleaner; +import com.esri.serverextension.core.util.StopWatch; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.dao.IncorrectResultSizeDataAccessException; + +import java.io.IOException; + +public class GeodatabaseObjectCallbackHandlerCursorExtractor implements + GeodatabaseCursorExtractor { + + private final Logger logger = LoggerFactory + .getLogger(GeodatabaseObjectMapperCursorExtractor.class); + + private final GeodatabaseObjectCallbackHandler objectCallbackHandler; + private final int objectsExpected; + + public GeodatabaseObjectCallbackHandlerCursorExtractor(GeodatabaseObjectCallbackHandler objectCallbackHandler) { + this(objectCallbackHandler, -1); + } + + public GeodatabaseObjectCallbackHandlerCursorExtractor(GeodatabaseObjectCallbackHandler objectCallbackHandler, int objectsExpected) { + this.objectCallbackHandler = objectCallbackHandler; + this.objectsExpected = objectsExpected; + } + + @Override + public Object extractData(ICursor cursor, IField[] fields) + throws IOException { + try { + StopWatch stopWatch = StopWatch.createAndStart(); + + objectCallbackHandler.setFields(fields); + + IRow row = null; + int rowCount = 0; + if ((row = cursor.nextRow()) != null) { + logger.debug("Executing query took {} second(s).", stopWatch + .stop().elapsedTimeSeconds()); + stopWatch.start(); + rowCount++; + objectCallbackHandler.processRow(row); + Cleaner.release(row); + } else { + logger.debug("Query returned no rows."); + return null; + } + if (objectsExpected == 1) { + if ((row = cursor.nextRow()) != null) { + Cleaner.release(row); + throw new IncorrectResultSizeDataAccessException( + "Expected just one row but found more.", 1); + } + } else { + while ((row = cursor.nextRow()) != null) { + rowCount++; + objectCallbackHandler.processRow(row); + Cleaner.release(row); + } + } + logger.debug("Extracting {} row(s) took {} second(s).", rowCount, + stopWatch.stop().elapsedTimeSeconds()); + return null; + } catch (AutomationException ex) { + throw new GeodatabaseSystemException("Failed to extract data from cursor.", + ex); + } finally { + if (cursor != null) { + Cleaner.release(cursor); + } + } + } + + @Override + public Object extractData(IFeatureCursor featureCursor, IField[] fields) throws IOException { + try { + StopWatch stopWatch = StopWatch.createAndStart(); + + objectCallbackHandler.setFields(fields); + + IFeature feature = null; + int featureCount = 0; + if ((feature = featureCursor.nextFeature()) != null) { + logger.debug("Executing query took {} second(s).", stopWatch + .stop().elapsedTimeSeconds()); + stopWatch.start(); + featureCount++; + objectCallbackHandler.processFeature(feature); + Cleaner.release(feature); + } else { + logger.debug("Query returned no features."); + return null; + } + if (objectsExpected == 1) { + if ((feature = featureCursor.nextFeature()) != null) { + Cleaner.release(feature); + throw new IncorrectResultSizeDataAccessException( + "Expected just one feature but found more.", 1); + } + } else { + while ((feature = featureCursor.nextFeature()) != null) { + featureCount++; + objectCallbackHandler.processFeature(feature); + Cleaner.release(feature); + } + } + logger.debug("Extracting {} features(s) took {} second(s).", featureCount, + stopWatch.stop().elapsedTimeSeconds()); + return null; + } catch (AutomationException ex) { + throw new GeodatabaseSystemException("Failed to extract data from cursor.", + ex); + } finally { + if (featureCursor != null) { + Cleaner.release(featureCursor); + } + } + } +} diff --git a/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/Feature.java b/server-extension-core/src/main/java/com/esri/serverextension/core/geodatabase/GeodatabaseObjectMapper.java similarity index 53% rename from examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/Feature.java rename to server-extension-core/src/main/java/com/esri/serverextension/core/geodatabase/GeodatabaseObjectMapper.java index e9c7d7e..8c86af1 100644 --- a/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/Feature.java +++ b/server-extension-core/src/main/java/com/esri/serverextension/core/geodatabase/GeodatabaseObjectMapper.java @@ -12,33 +12,21 @@ * limitations under the License.​ */ -package com.esri.serverextension.cluster; +package com.esri.serverextension.core.geodatabase; -/** - * Created by kcoffin on 2/8/17. - */ -public class Feature { +import java.io.IOException; - private double _value; - private Point _point; +import com.esri.arcgis.geodatabase.IFeature; +import com.esri.arcgis.geodatabase.IFeatureClass; +import com.esri.arcgis.geodatabase.IField; +import com.esri.arcgis.geodatabase.IRow; - public Feature(Point point, double value) { - _point = point; - _value = value; - } +public interface GeodatabaseObjectMapper { - public double getValue() { - return _value; - } + public void setFields(IField[] fields) throws IOException; + public T mapRow(IRow row) throws IOException; - public Point getPoint() { - return _point; - } + public T mapFeature(IFeature feature) throws IOException; - public void print() { - System.out.print("Feature value:" + _value + " "); - _point.print(); - System.out.println(); - } } diff --git a/server-extension-core/src/main/java/com/esri/serverextension/core/geodatabase/GeodatabaseObjectMapperCursorExtractor.java b/server-extension-core/src/main/java/com/esri/serverextension/core/geodatabase/GeodatabaseObjectMapperCursorExtractor.java new file mode 100644 index 0000000..79a91fa --- /dev/null +++ b/server-extension-core/src/main/java/com/esri/serverextension/core/geodatabase/GeodatabaseObjectMapperCursorExtractor.java @@ -0,0 +1,143 @@ +/* + * Copyright (c) 2017 Esri + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License.​ + */ + +package com.esri.serverextension.core.geodatabase; + +import com.esri.arcgis.geodatabase.*; +import com.esri.arcgis.interop.AutomationException; +import com.esri.arcgis.system.Cleaner; +import com.esri.serverextension.core.util.StopWatch; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.dao.IncorrectResultSizeDataAccessException; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +public class GeodatabaseObjectMapperCursorExtractor implements + GeodatabaseCursorExtractor> { + + private final Logger logger = LoggerFactory + .getLogger(GeodatabaseObjectMapperCursorExtractor.class); + + private final GeodatabaseObjectMapper objectMapper; + private final int objectsExpected; + + public GeodatabaseObjectMapperCursorExtractor(GeodatabaseObjectMapper objectMapper) { + this(objectMapper, -1); + } + + public GeodatabaseObjectMapperCursorExtractor(GeodatabaseObjectMapper objectMapper, int objectsExpected) { + this.objectMapper = objectMapper; + this.objectsExpected = objectsExpected; + } + + @Override + public List extractData(ICursor cursor, IField[] fields) + throws IOException { + try { + StopWatch stopWatch = StopWatch.createAndStart(); + + objectMapper.setFields(fields); + + List resultSet = objectsExpected > 0 ? new ArrayList( + objectsExpected) : new ArrayList(); + IRow row = null; + int rowCount = 0; + if ((row = cursor.nextRow()) != null) { + logger.debug("Executing query took {} second(s).", stopWatch + .stop().elapsedTimeSeconds()); + stopWatch.start(); + rowCount++; + resultSet.add(objectMapper.mapRow(row)); + Cleaner.release(row); + } else { + logger.debug("Query returned no rows."); + return resultSet; + } + if (objectsExpected == 1) { + if ((row = cursor.nextRow()) != null) { + Cleaner.release(row); + throw new IncorrectResultSizeDataAccessException( + "Expected just one row but found more.", 1); + } + } else { + while ((row = cursor.nextRow()) != null) { + rowCount++; + resultSet.add(objectMapper.mapRow(row)); + Cleaner.release(row); + } + } + logger.debug("Extracting {} row(s) took {} second(s).", rowCount, + stopWatch.stop().elapsedTimeSeconds()); + return resultSet; + } catch (AutomationException ex) { + throw new GeodatabaseSystemException("Failed to extract data from cursor.", + ex); + } finally { + if (cursor != null) { + Cleaner.release(cursor); + } + } + } + + @Override + public List extractData(IFeatureCursor featureCursor, IField[] fields) throws IOException { + try { + StopWatch stopWatch = StopWatch.createAndStart(); + + objectMapper.setFields(fields); + + List resultSet = objectsExpected > 0 ? new ArrayList( + objectsExpected) : new ArrayList(); + IFeature feature = null; + int featureCount = 0; + if ((feature = featureCursor.nextFeature()) != null) { + logger.debug("Executing query took {} second(s).", stopWatch + .stop().elapsedTimeSeconds()); + stopWatch.start(); + featureCount++; + resultSet.add(objectMapper.mapRow(feature)); + Cleaner.release(feature); + } else { + logger.debug("Query returned no features."); + return resultSet; + } + if (objectsExpected == 1) { + if ((feature = featureCursor.nextFeature()) != null) { + Cleaner.release(feature); + throw new IncorrectResultSizeDataAccessException( + "Expected just one feature but found more.", 1); + } + } else { + while ((feature = featureCursor.nextFeature()) != null) { + featureCount++; + resultSet.add(objectMapper.mapRow(feature)); + Cleaner.release(feature); + } + } + logger.debug("Extracting {} features(s) took {} second(s).", featureCount, + stopWatch.stop().elapsedTimeSeconds()); + return resultSet; + } catch (AutomationException ex) { + throw new GeodatabaseSystemException("Failed to extract data from cursor.", + ex); + } finally { + if (featureCursor != null) { + Cleaner.release(featureCursor); + } + } + } +} diff --git a/server-extension-core/src/main/java/com/esri/serverextension/core/geodatabase/GeodatabaseSystemException.java b/server-extension-core/src/main/java/com/esri/serverextension/core/geodatabase/GeodatabaseSystemException.java new file mode 100644 index 0000000..240d34b --- /dev/null +++ b/server-extension-core/src/main/java/com/esri/serverextension/core/geodatabase/GeodatabaseSystemException.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2017 Esri + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License.​ + */ + +package com.esri.serverextension.core.geodatabase; + +import org.springframework.dao.UncategorizedDataAccessException; + +import com.esri.arcgis.interop.AutomationException; + +public class GeodatabaseSystemException extends UncategorizedDataAccessException { + + private static final long serialVersionUID = 1L; + + public GeodatabaseSystemException(AutomationException cause) { + this(null, cause); + } + + public GeodatabaseSystemException(String msg, AutomationException cause) { + super(msg, cause); + } + +} diff --git a/server-extension-core/src/main/java/com/esri/serverextension/core/geodatabase/GeodatabaseTemplate.java b/server-extension-core/src/main/java/com/esri/serverextension/core/geodatabase/GeodatabaseTemplate.java new file mode 100644 index 0000000..040d6ab --- /dev/null +++ b/server-extension-core/src/main/java/com/esri/serverextension/core/geodatabase/GeodatabaseTemplate.java @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2017 Esri + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License.​ + */ + +package com.esri.serverextension.core.geodatabase; + +import com.esri.arcgis.geodatabase.*; +import com.esri.arcgis.interop.AutomationException; +import com.esri.arcgis.system.Cleaner; +import com.esri.serverextension.core.util.StopWatch; +import com.esri.serverextension.core.util.UncheckedIOException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +public class GeodatabaseTemplate { + + private final Logger logger = LoggerFactory.getLogger(GeodatabaseTemplate.class); + + public GeodatabaseTemplate() { + } + + public List query(IFeatureClass featureClass, IQueryFilter queryFilter, GeodatabaseObjectMapper objectMapper) { + return query(featureClass, queryFilter, new GeodatabaseObjectMapperCursorExtractor(objectMapper)); + } + + public T queryForObject(IFeatureClass featureClass, IQueryFilter queryFilter, + GeodatabaseObjectMapper objectMapper) { + List resultSet = query(featureClass, queryFilter, new GeodatabaseObjectMapperCursorExtractor( + objectMapper, 1)); + return resultSet.size() == 1 ? resultSet.get(0) : null; + } + + public void query(IFeatureClass featureClass, IQueryFilter queryFilter, + GeodatabaseObjectCallbackHandler objectCallbackHandler) { + query(featureClass, queryFilter, new GeodatabaseObjectCallbackHandlerCursorExtractor( + objectCallbackHandler)); + } + + public T query(IFeatureClass featureClass, IQueryFilter queryFilter, + GeodatabaseCursorExtractor cursorExtractor) { + IFeatureCursor cursor = null; + T result = null; + try { + StopWatch stopWatch = StopWatch.createAndStart(); + cursor = featureClass.search(queryFilter, false); + logger.debug("Preparing query took {} second(s).", stopWatch.stop() + .elapsedTimeSeconds()); + stopWatch.start(); + IField[] fields = toFieldsArray(cursor.getFields()); + result = cursorExtractor.extractData(cursor, fields); + logger.debug( + "Executing query and extracting data from cursor took {} second(s).", + stopWatch.stop().elapsedTimeSeconds()); + } catch (IOException ex) { + throw new UncheckedIOException("Failed to execute query.", ex); + } finally { + if (cursor != null) { + Cleaner.release(cursor); + } + } + return result; + } + + private static final IField[] toFieldsArray(final IFields fields) + throws AutomationException, IOException { + int fieldCount = fields.getFieldCount(); + List fieldList = new ArrayList<>(fieldCount); + for (int i = 0; i < fieldCount; i++) { + fieldList.add(fields.getField(i)); + } + return fieldList.toArray(new IField[fieldCount]); + } +} diff --git a/server-extension-core/src/main/java/com/esri/serverextension/core/rest/api/DimensionalDefinition.java b/server-extension-core/src/main/java/com/esri/serverextension/core/rest/api/DimensionalDefinition.java index 20bccb3..c9c1e63 100644 --- a/server-extension-core/src/main/java/com/esri/serverextension/core/rest/api/DimensionalDefinition.java +++ b/server-extension-core/src/main/java/com/esri/serverextension/core/rest/api/DimensionalDefinition.java @@ -16,6 +16,7 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; +import org.apache.commons.lang3.builder.ToStringBuilder; import javax.xml.bind.annotation.XmlRootElement; import java.io.Serializable; @@ -68,4 +69,9 @@ public boolean isSlice() { public void setSlice(boolean slice) { this.slice = slice; } + + @Override + public String toString() { + return ToStringBuilder.reflectionToString(this); + } } diff --git a/server-extension-core/src/main/java/com/esri/serverextension/core/rest/api/Extent.java b/server-extension-core/src/main/java/com/esri/serverextension/core/rest/api/Extent.java new file mode 100644 index 0000000..ce32cfb --- /dev/null +++ b/server-extension-core/src/main/java/com/esri/serverextension/core/rest/api/Extent.java @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2017 Esri + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License.​ + */ + +package com.esri.serverextension.core.rest.api; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import org.apache.commons.lang3.builder.ToStringBuilder; + +import javax.xml.bind.annotation.XmlRootElement; +import java.io.Serializable; + +@XmlRootElement +@JsonIgnoreProperties(ignoreUnknown = true) +public class Extent implements Serializable { + + private static final long serialVersionUID = 1L; + + private Double xmin; + private Double ymin; + private Double xmax; + private Double ymax; + + public Extent() { + + } + + public Double getXmin() { + return xmin; + } + + public void setXmin(Double xmin) { + this.xmin = xmin; + } + + public Double getYmin() { + return ymin; + } + + public void setYmin(Double ymin) { + this.ymin = ymin; + } + + public Double getXmax() { + return xmax; + } + + public void setXmax(Double xmax) { + this.xmax = xmax; + } + + public Double getYmax() { + return ymax; + } + + public void setYmax(Double ymax) { + this.ymax = ymax; + } + + public double getWidth() { + return xmax - xmin; + } + + public double getHeight() { + return ymax - ymin; + } + + @Override + public String toString() { + return ToStringBuilder.reflectionToString(this); + } +} \ No newline at end of file diff --git a/server-extension-core/src/main/java/com/esri/serverextension/core/rest/api/Feature.java b/server-extension-core/src/main/java/com/esri/serverextension/core/rest/api/Feature.java new file mode 100644 index 0000000..c4cb492 --- /dev/null +++ b/server-extension-core/src/main/java/com/esri/serverextension/core/rest/api/Feature.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2017 Esri + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License.​ + */ + +package com.esri.serverextension.core.rest.api; + + +import com.esri.arcgis.geometry.IGeometry; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import org.apache.commons.lang3.builder.ToStringBuilder; + +import javax.xml.bind.annotation.XmlRootElement; +import java.io.Serializable; +import java.util.Map; + +@XmlRootElement +@JsonIgnoreProperties(ignoreUnknown = true) +public class Feature implements Serializable { + + private static final long serialVersionUID = 1L; + + private IGeometry geometry; + private Map attributes; + + public Feature() { + } + + public IGeometry getGeometry() { + return geometry; + } + + public void setGeometry(IGeometry geometry) { + this.geometry = geometry; + } + + public Map getAttributes() { + return attributes; + } + + public void setAttributes(Map attributes) { + this.attributes = attributes; + } + + @Override + public String toString() { + return ToStringBuilder.reflectionToString(this); + } +} diff --git a/server-extension-core/src/main/java/com/esri/serverextension/core/rest/api/FeatureSet.java b/server-extension-core/src/main/java/com/esri/serverextension/core/rest/api/FeatureSet.java new file mode 100644 index 0000000..abc731d --- /dev/null +++ b/server-extension-core/src/main/java/com/esri/serverextension/core/rest/api/FeatureSet.java @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2017 Esri + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License.​ + */ + +package com.esri.serverextension.core.rest.api; + +import com.esri.arcgis.geometry.ISpatialReference; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import org.apache.commons.lang3.builder.ToStringBuilder; + +import javax.xml.bind.annotation.XmlRootElement; +import java.io.Serializable; +import java.util.List; + +@XmlRootElement +@JsonIgnoreProperties(ignoreUnknown = true) +public class FeatureSet implements Serializable { + + private static final long serialVersionUID = 1L; + + private String objectIdFieldName; //optional + private String globalIdFieldName; //optional + private String displayFieldName; //optional + private GeometryType geometryType; //for feature layers only + private ISpatialReference spatialReference; //for feature layers only + @JsonProperty("hasZ") + private boolean hasZ; //optional Default is false. + @JsonProperty("hasM") + private boolean hasM; //optional Default is false. + private List fields; + private List features; //features will include geometry for feature layers only + + public FeatureSet() { + + } + + public String getObjectIdFieldName() { + return objectIdFieldName; + } + + public void setObjectIdFieldName(String objectIdFieldName) { + this.objectIdFieldName = objectIdFieldName; + } + + public String getGlobalIdFieldName() { + return globalIdFieldName; + } + + public void setGlobalIdFieldName(String globalIdFieldName) { + this.globalIdFieldName = globalIdFieldName; + } + + public String getDisplayFieldName() { + return displayFieldName; + } + + public void setDisplayFieldName(String displayFieldName) { + this.displayFieldName = displayFieldName; + } + + public GeometryType getGeometryType() { + return geometryType; + } + + public void setGeometryType(GeometryType geometryType) { + this.geometryType = geometryType; + } + + public ISpatialReference getSpatialReference() { + return spatialReference; + } + + public void setSpatialReference(ISpatialReference spatialReference) { + this.spatialReference = spatialReference; + } + + public boolean isHasZ() { + return hasZ; + } + + public void setHasZ(boolean hasZ) { + this.hasZ = hasZ; + } + + public boolean isHasM() { + return hasM; + } + + public void setHasM(boolean hasM) { + this.hasM = hasM; + } + + public List getFields() { + return fields; + } + + public void setFields(List fields) { + this.fields = fields; + } + + public List getFeatures() { + return features; + } + + public void setFeatures(List features) { + this.features = features; + } + + @Override + public String toString() { + return ToStringBuilder.reflectionToString(this); + } +} diff --git a/server-extension-core/src/main/java/com/esri/serverextension/core/rest/api/Field.java b/server-extension-core/src/main/java/com/esri/serverextension/core/rest/api/Field.java new file mode 100644 index 0000000..5f60b0a --- /dev/null +++ b/server-extension-core/src/main/java/com/esri/serverextension/core/rest/api/Field.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2017 Esri + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License.​ + */ + +package com.esri.serverextension.core.rest.api; + +import java.io.Serializable; + +import javax.xml.bind.annotation.XmlRootElement; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import org.apache.commons.lang3.builder.ToStringBuilder; + +@XmlRootElement +@JsonIgnoreProperties(ignoreUnknown = true) +public class Field implements Serializable { + + private static final long serialVersionUID = 1L; + + private String name; + private FieldType type; + private String alias; + private Integer length; + + public Field() { + } + + public Field(String name, String alias) { + this.name = name; + this.alias = alias; + } + + public Field(String name, FieldType type, String alias) { + this.name = name; + this.type = type; + this.alias = alias; + } + + public Field(String name, FieldType type, String alias, Integer length) { + this.name = name; + this.type = type; + this.alias = alias; + this.length = length; + } + + public String getName() { + return name; + } + + public FieldType getType() { + return type; + } + + public String getAlias() { + return alias; + } + + public Integer getLength() { + return length; + } + + @Override + public String toString() { + return ToStringBuilder.reflectionToString(this); + } + +} diff --git a/server-extension-core/src/main/java/com/esri/serverextension/core/rest/api/FieldType.java b/server-extension-core/src/main/java/com/esri/serverextension/core/rest/api/FieldType.java new file mode 100644 index 0000000..6ae9ba4 --- /dev/null +++ b/server-extension-core/src/main/java/com/esri/serverextension/core/rest/api/FieldType.java @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2017 Esri + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License.​ + */ + +package com.esri.serverextension.core.rest.api; + +import java.io.Serializable; + +import javax.xml.bind.annotation.XmlType; + +@XmlType +public enum FieldType implements Serializable { + esriFieldTypeSmallInteger, esriFieldTypeInteger, esriFieldTypeSingle, esriFieldTypeDouble, esriFieldTypeString, esriFieldTypeDate, esriFieldTypeOID, esriFieldTypeGeometry, esriFieldTypeBlob, esriFieldTypeRaster, esriFieldTypeGUID, esriFieldTypeGlobalID, esriFieldTypeXML +} diff --git a/server-extension-core/src/main/java/com/esri/serverextension/core/rest/api/QueryMapServiceLayerOperationInput.java b/server-extension-core/src/main/java/com/esri/serverextension/core/rest/api/QueryMapServiceLayerOperationInput.java index b90d3dd..0e5dae1 100644 --- a/server-extension-core/src/main/java/com/esri/serverextension/core/rest/api/QueryMapServiceLayerOperationInput.java +++ b/server-extension-core/src/main/java/com/esri/serverextension/core/rest/api/QueryMapServiceLayerOperationInput.java @@ -14,13 +14,21 @@ package com.esri.serverextension.core.rest.api; +import com.esri.arcgis.geodatabase.IQueryFilter; +import com.esri.arcgis.geodatabase.ISpatialFilter; +import com.esri.arcgis.geodatabase.SpatialFilter; +import com.esri.arcgis.geometry.esriSpatialRelationEnum; import com.esri.arcgis.geometry.IGeometry; import com.esri.arcgis.geometry.ISpatialReference; +import com.esri.serverextension.core.util.ArcObjectsInteropException; +import com.esri.serverextension.core.util.GenericEsriEnum; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; +import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.builder.ToStringBuilder; import javax.xml.bind.annotation.XmlRootElement; +import java.io.IOException; import java.util.List; import java.util.Map; @@ -333,4 +341,5 @@ public void setRelationParam(String relationParam) { public String toString() { return ToStringBuilder.reflectionToString(this); } + } diff --git a/server-extension-core/src/main/java/com/esri/serverextension/core/rest/api/RangeValue.java b/server-extension-core/src/main/java/com/esri/serverextension/core/rest/api/RangeValue.java index 34bc2ee..bf1eaf6 100644 --- a/server-extension-core/src/main/java/com/esri/serverextension/core/rest/api/RangeValue.java +++ b/server-extension-core/src/main/java/com/esri/serverextension/core/rest/api/RangeValue.java @@ -15,6 +15,7 @@ package com.esri.serverextension.core.rest.api; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import org.apache.commons.lang3.builder.ToStringBuilder; import javax.xml.bind.annotation.XmlRootElement; import java.io.Serializable; @@ -46,4 +47,9 @@ public Object getValue() { public void setValue(Object value) { this.value = value; } + + @Override + public String toString() { + return ToStringBuilder.reflectionToString(this); + } } diff --git a/server-extension-core/src/main/java/com/esri/serverextension/core/rest/api/RenderingRule.java b/server-extension-core/src/main/java/com/esri/serverextension/core/rest/api/RenderingRule.java index 3bade7d..575e3c9 100644 --- a/server-extension-core/src/main/java/com/esri/serverextension/core/rest/api/RenderingRule.java +++ b/server-extension-core/src/main/java/com/esri/serverextension/core/rest/api/RenderingRule.java @@ -15,6 +15,7 @@ package com.esri.serverextension.core.rest.api; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import org.apache.commons.lang3.builder.ToStringBuilder; import javax.xml.bind.annotation.XmlRootElement; import java.io.Serializable; @@ -68,4 +69,9 @@ public String getVariableName() { public void setVariableName(String variableName) { this.variableName = variableName; } + + @Override + public String toString() { + return ToStringBuilder.reflectionToString(this); + } } diff --git a/server-extension-core/src/main/java/com/esri/serverextension/core/rest/api/Transformation.java b/server-extension-core/src/main/java/com/esri/serverextension/core/rest/api/Transformation.java index 6b8d020..e7b35a0 100644 --- a/server-extension-core/src/main/java/com/esri/serverextension/core/rest/api/Transformation.java +++ b/server-extension-core/src/main/java/com/esri/serverextension/core/rest/api/Transformation.java @@ -16,6 +16,7 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; +import org.apache.commons.lang3.builder.ToStringBuilder; import javax.xml.bind.annotation.XmlRootElement; import java.io.Serializable; @@ -48,4 +49,9 @@ public boolean isTransformForward() { public void setTransformForward(boolean transformForward) { this.transformForward = transformForward; } + + @Override + public String toString() { + return ToStringBuilder.reflectionToString(this); + } } diff --git a/server-extension-core/src/main/java/com/esri/serverextension/core/server/internal/DelegateMethodInvoker.java b/server-extension-core/src/main/java/com/esri/serverextension/core/server/internal/DelegateMethodInvoker.java index c280ff7..0c0b702 100644 --- a/server-extension-core/src/main/java/com/esri/serverextension/core/server/internal/DelegateMethodInvoker.java +++ b/server-extension-core/src/main/java/com/esri/serverextension/core/server/internal/DelegateMethodInvoker.java @@ -88,7 +88,7 @@ public RestResponse process(RestRequest request, RestDelegate handler) { + "delegate method '%2$s' in '%3$s' with the following " + "arguments: %4$s", returnValue, methodName, className, Arrays.toString(arguments)); - throw new ServerObjectExtensionException(message, ex.getCause()); + throw new ServerObjectExtensionException(message, ex); } return response; diff --git a/server-extension-core/src/main/java/com/esri/serverextension/core/util/GenericEsriEnum.java b/server-extension-core/src/main/java/com/esri/serverextension/core/util/GenericEsriEnum.java new file mode 100644 index 0000000..ba649e3 --- /dev/null +++ b/server-extension-core/src/main/java/com/esri/serverextension/core/util/GenericEsriEnum.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2017 Esri + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License.​ + */ + +package com.esri.serverextension.core.util; + +import com.esri.serverextension.core.rest.json.JSONException; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; + +public class GenericEsriEnum { + + private GenericEsriEnum() { + } + + /** + * Deserializer for Esri-style enums, which are interfaces with public static + * int members, e.g. com.esri.arcgis.geometry.esriGeometryType. This method converts + * the string representation of one of the fields, e.g. esriGeometryPoint, to + * its equivalent int representation, e.g. 1, given the specified Esri-style enum class. + * + * @param esriEnum The Esri-style enum class used to deserialize a string value. + * @param enumValue The name of a public static integer field in the Esri-style enum class. + * @param The Esri-style enum class used to deserialize a string value. + * @return the integer value for the specified string + */ + public static final Integer valueOf(Class esriEnum, String enumValue) { + Field[] fields = esriEnum.getDeclaredFields(); + for (Field field : fields) { + if (!Modifier.isStatic(field.getModifiers())) { + continue; + } + if (!Modifier.isPublic(field.getModifiers())) { + continue; + } + if (!field.getType().equals(Integer.TYPE)) { + continue; + } + if (field.getName().equals(enumValue)) { + try { + return field.getInt(null); + } catch (IllegalAccessException ex) { + throw new JSONException("Cannot access property.", ex); + } + } + } + throw new IllegalArgumentException(String.format( + "Cannot find field %1$s in class %2$s", enumValue, esriEnum.getName())); + } +} diff --git a/server-extension-core/src/main/java/com/esri/serverextension/core/util/StopWatch.java b/server-extension-core/src/main/java/com/esri/serverextension/core/util/StopWatch.java index 27bc236..d063301 100644 --- a/server-extension-core/src/main/java/com/esri/serverextension/core/util/StopWatch.java +++ b/server-extension-core/src/main/java/com/esri/serverextension/core/util/StopWatch.java @@ -30,22 +30,24 @@ public static StopWatch createAndStart() { return timer; } - public void start() { + public StopWatch start() { if (isRunning) { throw new IllegalStateException("StopWatch is already running."); } isRunning = true; hasElapsed = false; startTime = System.currentTimeMillis(); + return this; } - public void stop() { + public StopWatch stop() { if (!isRunning) { throw new IllegalStateException("StopWatch is not running."); } elapsedTime = System.currentTimeMillis() - startTime; isRunning = false; hasElapsed = true; + return this; } public long elapsedTimeMillis() { @@ -53,7 +55,7 @@ public long elapsedTimeMillis() { throw new IllegalStateException("StopWatch is already running."); } if (!hasElapsed) { - throw new IllegalStateException("StopWatch was not run."); + throw new IllegalStateException("StopWatch has never been started."); } return elapsedTime; } From fffbb161a8610adfaab4199b67df26e0db36cc8e Mon Sep 17 00:00:00 2001 From: Carsten Piepel Date: Wed, 8 Mar 2017 00:39:07 -0800 Subject: [PATCH 10/13] Includes client application. --- .../attribute-security-filter-soi.iml | 9 +- examples/clustering-soe/client/index.html | 201 ++++++++++ .../client/js/layer/ClusterQuery.js | 28 ++ .../client/js/layer/ServerClusterLayer.js | 359 ++++++++++++++++++ .../cluster/ClusterAssembler.java | 23 +- .../ClusterAssemblerCallbackHandler.java | 11 +- .../cluster/ClusterLayerResource.java | 11 +- .../cluster/ClusterAssemblerIT.java | 85 +++++ .../test/AbstractArcObjectsIT.java | 39 ++ .../src/test/resources/log4j.xml | 16 + ...icationContext-file-gdb-workspace-test.xml | 26 ++ pom.xml | 40 +- server-extension-core/pom.xml | 27 -- .../FileGDBWorkspaceFactoryBean.java | 93 +++++ .../GeodatabaseCursorExtractor.java | 4 +- .../core/geodatabase/GeodatabaseFieldMap.java | 101 +++++ .../GeodatabaseObjectCallbackHandler.java | 2 +- ...eObjectCallbackHandlerCursorExtractor.java | 8 +- .../geodatabase/GeodatabaseObjectMapper.java | 2 +- ...eodatabaseObjectMapperCursorExtractor.java | 8 +- .../core/geodatabase/GeodatabaseTemplate.java | 16 +- .../core/rest/api/FeatureSet.java | 10 + 22 files changed, 1042 insertions(+), 77 deletions(-) create mode 100644 examples/clustering-soe/client/index.html create mode 100644 examples/clustering-soe/client/js/layer/ClusterQuery.js create mode 100644 examples/clustering-soe/client/js/layer/ServerClusterLayer.js create mode 100644 examples/clustering-soe/src/test/java/com/esri/serverextension/cluster/ClusterAssemblerIT.java create mode 100644 examples/clustering-soe/src/test/java/com/esri/serverextension/test/AbstractArcObjectsIT.java create mode 100644 examples/clustering-soe/src/test/resources/log4j.xml create mode 100644 examples/clustering-soe/src/test/resources/spring/config/applicationContext-file-gdb-workspace-test.xml create mode 100644 server-extension-core/src/main/java/com/esri/serverextension/core/geodatabase/FileGDBWorkspaceFactoryBean.java create mode 100644 server-extension-core/src/main/java/com/esri/serverextension/core/geodatabase/GeodatabaseFieldMap.java diff --git a/examples/attribute-security-filter-soi/attribute-security-filter-soi.iml b/examples/attribute-security-filter-soi/attribute-security-filter-soi.iml index 223af81..bce9c76 100644 --- a/examples/attribute-security-filter-soi/attribute-security-filter-soi.iml +++ b/examples/attribute-security-filter-soi/attribute-security-filter-soi.iml @@ -20,9 +20,6 @@ - - - @@ -65,6 +62,12 @@ + + + + + + \ No newline at end of file diff --git a/examples/clustering-soe/client/index.html b/examples/clustering-soe/client/index.html new file mode 100644 index 0000000..edb8361 --- /dev/null +++ b/examples/clustering-soe/client/index.html @@ -0,0 +1,201 @@ + + + + + + Serverside Clustering + + + + + + + + + + + + + + + + + +
+
+ +
+ + + +
+
+ + +
+ + + + + + + + +
+ + + diff --git a/examples/clustering-soe/client/js/layer/ClusterQuery.js b/examples/clustering-soe/client/js/layer/ClusterQuery.js new file mode 100644 index 0000000..7acb251 --- /dev/null +++ b/examples/clustering-soe/client/js/layer/ClusterQuery.js @@ -0,0 +1,28 @@ +define([ + "dojo/_base/declare", + "esri/tasks/query" + + +], function(declare, Query){ + return declare("ClusterQuery", [Query], { + constructor: function(map){ + this.map = map; + this.clusterDistanceInPixels = 100; + this.clusterField = null; + }, + + toJson:function(){ + var obj = this.inherited(arguments); + obj.bbox = JSON.stringify(this.map.extent); + obj.mapUnitsPerPixel = this.map.extent.getWidth()/this.map.width; + obj.clusterDistanceInPixels = this.clusterDistanceInPixels; + obj.clusterField = this.clusterField; + + return obj; + } + + }); + + + +}); \ No newline at end of file diff --git a/examples/clustering-soe/client/js/layer/ServerClusterLayer.js b/examples/clustering-soe/client/js/layer/ServerClusterLayer.js new file mode 100644 index 0000000..f66b03f --- /dev/null +++ b/examples/clustering-soe/client/js/layer/ServerClusterLayer.js @@ -0,0 +1,359 @@ +define([ + "dojo/_base/declare", + "dojo/_base/array", + "dojo/number", + "esri/Color", + "dojo/_base/connect", + "esri/renderers/ClassBreaksRenderer", + + "esri/SpatialReference", + "esri/geometry/Point", + "esri/graphic", + "esri/symbols/SimpleMarkerSymbol", + "esri/symbols/TextSymbol", + "esri/symbols/SimpleLineSymbol", + "esri/symbols/Font", + + "esri/dijit/PopupTemplate", + "esri/layers/GraphicsLayer", + "dojo/_base/lang", + "esri/tasks/QueryTask", + + "layer/ClusterQuery" + +], function ( + declare, arrayUtils, Number, Color, connect, ClassBreaksRenderer, + SpatialReference, Point, Graphic, SimpleMarkerSymbol, TextSymbol, SimpleLineSymbol, Font, + PopupTemplate, GraphicsLayer, lang, QueryTask, ClusterQuery +) { + return declare([GraphicsLayer], { + constructor: function(options) { + this.url = options.url; + }, + + // override esri/layers/GraphicsLayer methods + _setMap: function(map, surface) { + this._saveMap = map; + + map.on("extent-change", lang.hitch(this, this.changeExtent)); + this.refreshLayer(); + // GraphicsLayer will add its own listener here + var div = this.inherited(arguments); + return div; + }, + + _unsetMap: function() { + this.inherited(arguments); + connect.disconnect(this._zoomEnd); + }, + + changeExtent:function(event){ + this.refreshLayer(); + }, + + stringifyAllBreaks:function(breaks){ + var sBreaks = []; + for (var i=0;i= 10000000000000000) { //10 quadrillion + value = Number.format(value1 / 1000000000000000, { + places: additionalPlaces + 0 + }) + "Q"; //1 quadrillion + } else if (value >= 1000000000000000) { //1 quadrillion + value = Number.format(value1 / 1000000000000000, { + places: additionalPlaces + 0 + }) + "Q"; //1 quadrillion + } else if (value >= 10000000000000) { //10 trillion + value = Number.format(value1 / 1000000000000, { + places: additionalPlaces + 0 + }) + "T"; //1 trillion + } else if (value >= 1050000000000) { //1 trillion + value = Number.format(value1 / 1000000000000, { + places: additionalPlaces + 1 + }) + "T"; //1 trillion + } else if (value >= 1000000000000) { //1 trillion + value = Number.format(value1 / 1000000000000, { + places: additionalPlaces + 0 + }) + "T"; //1 trillion + } else if (value >= 10000000000) { //10 billion + value = Number.format(value1 / 1000000000, { + places: additionalPlaces + 0 + }) + "B"; + } else if (value >= 1050000000) { //1.05 billion + value = Number.format(value1 / 1000000000, { + places: additionalPlaces + 1 + }) + "B"; + } else if (value >= 1000000000) { //1 billion + value = Number.format(value1 / 1000000000, { + places: additionalPlaces + 0 + }) + "B"; + } else if (value >= 10000000) { //10 million + value = Number.format(value1 / 1000000, { + places: additionalPlaces + 0 + }) + "M"; + } else if (value >= 1050000) { //1.05 million + value = Number.format(value1 / 1000000, { + places: additionalPlaces + 1 + }) + "M"; + } else if (value >= 1000000) { //1 million + value = Number.format(value1 / 1000000, { + places: additionalPlaces + 0 + }) + "M"; + } else if (value >= 10000) { // 10K + value = Number.format(value1 / 1000, { + places: additionalPlaces + 0 + }) + "K"; + } else if (value >= 1050) { + value = Number.format(value1 / 1000, { + places: additionalPlaces + 1 + }) + "K"; + } else if (value >= 1000) { + value = Number.format(value1 / 1000, { + places: additionalPlaces + 0 + }) + "K"; + } else if (value >= 1 ) { + value = Number.format(value1, { + places: Math.min(additionalPlaces + 0, numPlaces) + }); + } else if (value >= 0.1) { + value = Number.format(value1, { + places: Math.min(additionalPlaces + 1, numPlaces) + }); + } else if (value >= 0.01) { + value = Number.format(value1, { + places: Math.min(additionalPlaces + 2, numPlaces) + }); + } else if (value >= 0.001) { + value = Number.format(value1, { + places: Math.min(additionalPlaces + 3, numPlaces) + }); + } else if (value >= 0.0001) { + value = Number.format(value1, { + places: Math.min(additionalPlaces + 4, numPlaces) + }); + } else if (value >= 0.00001) { + value = Number.format(value1, { + places: Math.min(additionalPlaces + 5, numPlaces) + }); + } else if (value >= 0.000001) { + value = Number.format(value1, { + places: Math.min(additionalPlaces + 6, numPlaces) + }); + } else { + value = Number.format(value1); + } + + } + } + return value; + }, + + + + getAllInfos:function(breaks){ + var newBreaks = this.stringifyAllBreaks(breaks); + var infoArray = []; + for (var i=0;i 5){ + var inc = Math.floor(features.length/5); + for (var j=0;j<4;j++){ + var feature = features[inc*j]; + var val = feature.attributes[fieldName]; + breaks.push(val); + /* + if (j<4){ + feature = features[inc*(j+1)]; + val = feature.attributes[fieldName]; + breaks.push(val); + } + */ + } + + breaks.push(features[features.length-1].attributes[fieldName]); + + }else if (features.length > 0){ + breaks.push(features[0].attributes[fieldName]); + breaks.push(features[features.length-1].attributes[fieldName]); + }else{ + breaks.push(0); + breaks.push(1); + } + + + + var white = new Color([255,255, 255, 0.7]); + var ringColor = new Color([0, 163, 98, 0.7]); + var symbols = []; + symbols[0] = new SimpleMarkerSymbol(SimpleMarkerSymbol.STYLE_CIRCLE, 28, + new SimpleLineSymbol(SimpleLineSymbol.STYLE_SOLID, ringColor, 3), + white); + symbols[1] = new SimpleMarkerSymbol(SimpleMarkerSymbol.STYLE_CIRCLE, 34, + new SimpleLineSymbol(SimpleLineSymbol.STYLE_SOLID, ringColor, 5), + white); + symbols[2] = new SimpleMarkerSymbol(SimpleMarkerSymbol.STYLE_CIRCLE, 40, + new SimpleLineSymbol(SimpleLineSymbol.STYLE_SOLID, ringColor, 8), + white); + symbols[3] = new SimpleMarkerSymbol(SimpleMarkerSymbol.STYLE_CIRCLE, 46, + new SimpleLineSymbol(SimpleLineSymbol.STYLE_SOLID, ringColor, 10), + white); + symbols[4] = new SimpleMarkerSymbol(SimpleMarkerSymbol.STYLE_CIRCLE, 52, + new SimpleLineSymbol(SimpleLineSymbol.STYLE_SOLID, ringColor, 12), + white); + + var renderer = new ClassBreaksRenderer(null, fieldName); + + var infos = this.getAllInfos(breaks); + + for (var k=0;k(); this.clusterFieldName = clusterFieldName; } @@ -93,12 +93,12 @@ public List getClusters() { * assembled clusters. */ public void buildClusters() { -// for (Cluster cluster : clusters) { -// fixCluster(cluster); -// } -// for (Cluster cluster : clusters) { -// fixCluster(cluster); -// } + for (Cluster cluster : clusters) { + fixCluster(cluster); + } + for (Cluster cluster : clusters) { + fixCluster(cluster); + } } /** @@ -150,7 +150,7 @@ private void removeClusterFromGrid(Cluster cluster) { if (cell != null) { cell.remove(cluster); } else { - System.out.println("Programming error"); + throw new IllegalStateException("Unexpected cluster state."); } } @@ -188,8 +188,7 @@ private Cluster getClosestCluster(ClusterPoint pt) { //should never happen as all features should come from within the extent if (row < 0 || column < 0 || row >= numRows || column >= numColumns) { - System.out.println("There's an error in the query"); - return null; + throw new IllegalStateException("There's an error in the query"); } int yStart = row; @@ -357,8 +356,6 @@ private void addCells(Cluster cluster) { cell.add(cluster); } } - - } private double getFeatureValue(Feature feature) { diff --git a/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/ClusterAssemblerCallbackHandler.java b/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/ClusterAssemblerCallbackHandler.java index 1e40604..c4ce1a5 100644 --- a/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/ClusterAssemblerCallbackHandler.java +++ b/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/ClusterAssemblerCallbackHandler.java @@ -17,6 +17,7 @@ import com.esri.arcgis.geodatabase.IFeature; import com.esri.arcgis.geodatabase.IField; import com.esri.arcgis.geodatabase.IRow; +import com.esri.serverextension.core.geodatabase.GeodatabaseFieldMap; import com.esri.serverextension.core.geodatabase.GeodatabaseObjectCallbackHandler; import com.esri.serverextension.core.rest.api.Feature; @@ -26,7 +27,7 @@ public class ClusterAssemblerCallbackHandler implements GeodatabaseObjectCallbackHandler { - private IField[] fields; + private GeodatabaseFieldMap fieldMap; private ClusterAssembler clusterAssembler; private int featureCount = 0; @@ -39,8 +40,8 @@ public int getFeatureCount() { } @Override - public void setFields(IField[] fields) throws IOException { - this.fields = fields; + public void setGeodatabaseFieldMap(GeodatabaseFieldMap fieldMap) throws IOException { + this.fieldMap = fieldMap; } @Override @@ -52,8 +53,8 @@ public void processRow(IRow row) throws IOException { public void processFeature(IFeature feature) throws IOException { featureCount++; Map attributes = new LinkedHashMap<>(); - for (int i = 0; i < fields.length; i++) { - attributes.put(fields[i].getName(), feature.getValue(i)); + for (GeodatabaseFieldMap.FieldIndex fieldIndex : fieldMap.getFieldIndices()) { + attributes.put(fieldIndex.getField().getName(), feature.getValue(fieldIndex.getIndex())); } Feature f = new Feature(); f.setGeometry(feature.getShape()); diff --git a/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/ClusterLayerResource.java b/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/ClusterLayerResource.java index b22851d..7f9bd9c 100644 --- a/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/ClusterLayerResource.java +++ b/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/ClusterLayerResource.java @@ -22,8 +22,8 @@ import com.esri.arcgis.interop.Cleaner; import com.esri.arcgis.server.json.JSONObject; import com.esri.serverextension.core.geodatabase.GeodatabaseTemplate; +import com.esri.serverextension.core.rest.api.*; import com.esri.serverextension.core.rest.api.Feature; -import com.esri.serverextension.core.rest.api.FeatureSet; import com.esri.serverextension.core.rest.api.Field; import com.esri.serverextension.core.rest.api.FieldType; import com.esri.serverextension.core.server.ServerObjectExtensionContext; @@ -82,11 +82,15 @@ public FeatureSet query(@PathVariable("layerId") int layerId, @BeanParam Cluster fields.add(field); featureSet.setFields(fields); featureSet.setSpatialReference(getOutSpatialReference(input, serverContext)); + featureSet.setGeometryType(GeometryType.esriGeometryPoint); clusterAssembler.buildClusters(); List clusters = clusterAssembler.getClusters(); if (!CollectionUtils.isEmpty(clusters)) { List features = new ArrayList<>(clusterAssembler.getClusters().size()); for (Cluster cluster : clusterAssembler.getClusters()) { + if (cluster.getValue() == 0.0d) { + continue; + } Feature clusterFeature = new Feature(); ClusterPoint clusterPoint = cluster.getPoint(); IPoint point = new Point(); @@ -117,9 +121,9 @@ public FeatureSet query(@PathVariable("layerId") int layerId, @BeanParam Cluster private IQueryFilter getQueryFilter(ClusterQueryOperationInput input, String shapeFieldName) { try { - IQueryFilter queryFilter = null; + IQueryFilter2 queryFilter = null; if (input.getGeometry() != null) { - ISpatialFilter spatialFilter = new SpatialFilter(); + SpatialFilter spatialFilter = new SpatialFilter(); spatialFilter.setGeometryByRef(input.getGeometry()); spatialFilter.setGeometryField(shapeFieldName); if (input.getSpatialRel() != null) { @@ -146,6 +150,7 @@ private IQueryFilter getQueryFilter(ClusterQueryOperationInput input, String sha "ORDER BY %1$s", input.getOrderByFields() )); } + queryFilter.setSpatialResolution(100000.0d); if (StringUtils.isNotEmpty(input.getClusterField())) { queryFilter.setSubFields(input.getClusterField()); queryFilter.addField(shapeFieldName); diff --git a/examples/clustering-soe/src/test/java/com/esri/serverextension/cluster/ClusterAssemblerIT.java b/examples/clustering-soe/src/test/java/com/esri/serverextension/cluster/ClusterAssemblerIT.java new file mode 100644 index 0000000..38a34a0 --- /dev/null +++ b/examples/clustering-soe/src/test/java/com/esri/serverextension/cluster/ClusterAssemblerIT.java @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2017 Esri + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License.​ + */ + +package com.esri.serverextension.cluster; + +import com.esri.arcgis.geodatabase.*; +import com.esri.arcgis.geometry.IEnvelope; +import com.esri.serverextension.core.geodatabase.FileGDBWorkspaceFactoryBean; +import com.esri.serverextension.core.geodatabase.GeodatabaseTemplate; +import com.esri.serverextension.core.rest.api.Extent; +import com.esri.serverextension.core.rest.json.JSONGeometryMapper; +import com.esri.serverextension.core.util.ArcObjectsInitializer; +import com.esri.serverextension.core.util.ArcObjectsUtilities; +import com.esri.serverextension.test.AbstractArcObjectsIT; +import net.jcip.annotations.NotThreadSafe; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import javax.inject.Inject; +import java.io.IOException; +import java.util.List; + +@RunWith(SpringJUnit4ClassRunner.class) +@NotThreadSafe +@ContextConfiguration(locations = {"/spring/config/applicationContext-file-gdb-workspace-test.xml"}) +public class ClusterAssemblerIT extends AbstractArcObjectsIT { + + @Inject + private IWorkspace workspace; + + @Test + public void testClusterAssembler() throws IOException { + Extent bbox = new Extent(); + bbox.setXmin(-13244092.36900171); + bbox.setYmin(4000883.3498998554); + bbox.setXmax(-13118812.079642477); + bbox.setYmax(4061574.350358204); + ClusterAssembler clusterAssembler = new ClusterAssembler(76.43702828507277, + 100, bbox, "Valuation"); + IFeatureClass featureClass = ((IFeatureWorkspace)workspace).openFeatureClass("Permit_Features"); + SpatialFilter spatialFilter = new SpatialFilter(); + spatialFilter.setSubFields("Valuation,Shape"); + spatialFilter.setGeometryField("Shape"); + spatialFilter.setSpatialRel(esriSpatialRelEnum.esriSpatialRelIntersects); + + JSONGeometryMapper geometryMapper = new JSONGeometryMapper(); + IEnvelope envelope = geometryMapper.readEnvelope("{\"xmin\":-13244092.36900171,\"ymin\":4000883.3498998554,\"xmax\":-13118812.079642477,\"ymax\":4061574.350358204,\"spatialReference\":{\"wkid\":102100}}"); + + spatialFilter.setGeometryByRef(envelope); + spatialFilter.setWhereClause("Issue_Date >= date '2017-01-01 00:00:00'"); + //spatialFilter.setWhereClause("OBJECTID < 10"); + spatialFilter.setOutputSpatialReferenceByRef("Shape", ArcObjectsUtilities.createSpatialReference(102100)); + GeodatabaseTemplate geodatabaseTemplate = new GeodatabaseTemplate(); + geodatabaseTemplate.query(featureClass, spatialFilter, new ClusterAssemblerCallbackHandler(clusterAssembler)); + List clusters = clusterAssembler.getClusters(); + int clusterCount = 0; + for (Cluster cluster : clusters) { + System.out.println(String.format("Cluster %1$d: (x: %2$f y: %3$f), %4$f", ++clusterCount, cluster.getPoint().x, cluster.getPoint().y, cluster.getValue())); + } + } + + public static void main(String[] args) throws Exception { + ArcObjectsInitializer.getInstance().init(); + FileGDBWorkspaceFactoryBean fileGDBWorkspaceFactoryBean = new FileGDBWorkspaceFactoryBean(); + fileGDBWorkspaceFactoryBean.setDatabase("D:\\Development\\Projects\\sever-extension-java\\examples\\clustering-soe\\data\\Clustering\\Clustering.gdb"); + IWorkspace workspace = fileGDBWorkspaceFactoryBean.getObject(); + ClusterAssemblerIT clusterAssemblerIT = new ClusterAssemblerIT(); + clusterAssemblerIT.workspace = workspace; + clusterAssemblerIT.testClusterAssembler(); + ArcObjectsInitializer.getInstance().shutdown(); + } +} diff --git a/examples/clustering-soe/src/test/java/com/esri/serverextension/test/AbstractArcObjectsIT.java b/examples/clustering-soe/src/test/java/com/esri/serverextension/test/AbstractArcObjectsIT.java new file mode 100644 index 0000000..efcff24 --- /dev/null +++ b/examples/clustering-soe/src/test/java/com/esri/serverextension/test/AbstractArcObjectsIT.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2017 Esri + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License.​ + */ + +package com.esri.serverextension.test; + +import com.esri.arcgis.interop.AutomationException; +import com.esri.serverextension.core.util.ArcObjectsInitializer; +import org.junit.AfterClass; +import org.junit.BeforeClass; + +import java.io.IOException; +import java.net.UnknownHostException; + +public abstract class AbstractArcObjectsIT { + + public AbstractArcObjectsIT() { + } + + @BeforeClass + public static void init() throws UnknownHostException, IOException { + ArcObjectsInitializer.getInstance().init(); + } + + @AfterClass + public static void shutdown() throws AutomationException, IOException { + ArcObjectsInitializer.getInstance().shutdown(); + } +} diff --git a/examples/clustering-soe/src/test/resources/log4j.xml b/examples/clustering-soe/src/test/resources/log4j.xml new file mode 100644 index 0000000..f5bacf5 --- /dev/null +++ b/examples/clustering-soe/src/test/resources/log4j.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/examples/clustering-soe/src/test/resources/spring/config/applicationContext-file-gdb-workspace-test.xml b/examples/clustering-soe/src/test/resources/spring/config/applicationContext-file-gdb-workspace-test.xml new file mode 100644 index 0000000..6f29500 --- /dev/null +++ b/examples/clustering-soe/src/test/resources/spring/config/applicationContext-file-gdb-workspace-test.xml @@ -0,0 +1,26 @@ + + + + + + + + + \ No newline at end of file diff --git a/pom.xml b/pom.xml index 2a97540..8283c61 100644 --- a/pom.xml +++ b/pom.xml @@ -33,7 +33,6 @@ 4.3.6.RELEASE UTF-8 UTF-8 - **/*IT.java ${env.JAVA_HOME} @@ -58,11 +57,46 @@ 4.12 test + + org.slf4j + slf4j-api + 1.7.24 + + + ch.qos.logback + logback-core + 1.2.1 + + + org.apache.commons + commons-lang3 + 3.5 + + org.springframework spring-test test + + log4j + log4j + 1.2.17 + test + + + org.slf4j + jcl-over-slf4j + 1.7.24 + test + + + + net.jcip + jcip-annotations + 1.0 + test + @@ -100,8 +134,10 @@ ArcObject's classes. It requires a properly activated installation of ArcGIS for Sever. --> -Djava.library.path="${env.AGSSERVER}/bin" + 1 + true - ${failsafe.exclude}/ + **/*IT.java diff --git a/server-extension-core/pom.xml b/server-extension-core/pom.xml index 5853bab..520302b 100644 --- a/server-extension-core/pom.xml +++ b/server-extension-core/pom.xml @@ -25,21 +25,6 @@ server-extension-core Shared library for building SOEs and SOIs - - org.slf4j - slf4j-api - 1.7.24 - - - ch.qos.logback - logback-core - 1.2.1 - - - org.apache.commons - commons-lang3 - 3.5 - commons-io commons-io @@ -103,17 +88,5 @@ system ${env.AGSSERVER}\framework\lib\arcobjects.jar - - - org.springframework - spring-test - test - - - org.slf4j - jcl-over-slf4j - 1.7.24 - test - \ No newline at end of file diff --git a/server-extension-core/src/main/java/com/esri/serverextension/core/geodatabase/FileGDBWorkspaceFactoryBean.java b/server-extension-core/src/main/java/com/esri/serverextension/core/geodatabase/FileGDBWorkspaceFactoryBean.java new file mode 100644 index 0000000..6cbd59d --- /dev/null +++ b/server-extension-core/src/main/java/com/esri/serverextension/core/geodatabase/FileGDBWorkspaceFactoryBean.java @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2017 Esri + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License.​ + */ + +package com.esri.serverextension.core.geodatabase; + +import org.springframework.beans.factory.DisposableBean; +import org.springframework.beans.factory.SmartFactoryBean; + +import com.esri.arcgis.datasourcesGDB.FileGDBWorkspaceFactory; +import com.esri.arcgis.geodatabase.IWorkspace; +import com.esri.arcgis.system.Cleaner; +import com.esri.arcgis.system.IPropertySet; +import com.esri.arcgis.system.PropertySet; + +public class FileGDBWorkspaceFactoryBean implements + SmartFactoryBean, DisposableBean { + + private String database; + private IWorkspace workspace; + + public FileGDBWorkspaceFactoryBean() { + } + + public String getDatabase() { + return database; + } + + public void setDatabase(String database) { + this.database = database; + } + + @Override + public IWorkspace getObject() throws Exception { + if (workspace == null) { + IPropertySet propertySet = new PropertySet(); + if (database != null) { + propertySet.setProperty("DATABASE", database); + } + FileGDBWorkspaceFactory fileGDBWorkspaceFactory = new FileGDBWorkspaceFactory(); + workspace = fileGDBWorkspaceFactory.open(propertySet, 0); + + // FileGDBWorkspaceFactory is a singleton. + // It is a good practice to release the singletons + // using com.esri.system.Cleaner.release() + Cleaner.release(fileGDBWorkspaceFactory); + } + return workspace; + } + + @Override + public Class getObjectType() { + return IWorkspace.class; + } + + @Override + public boolean isSingleton() { + return true; + } + + @Override + public boolean isEagerInit() { + return false; + } + + @Override + public boolean isPrototype() { + return false; + } + + @Override + public void destroy() throws Exception { + if (workspace != null) { + Cleaner.release(workspace); + workspace = null; + } + } + + @Override + public String toString() { + return "FileGDBWorkspaceFactoryBean [database=" + database + "]"; + } +} diff --git a/server-extension-core/src/main/java/com/esri/serverextension/core/geodatabase/GeodatabaseCursorExtractor.java b/server-extension-core/src/main/java/com/esri/serverextension/core/geodatabase/GeodatabaseCursorExtractor.java index 32668eb..ca29564 100644 --- a/server-extension-core/src/main/java/com/esri/serverextension/core/geodatabase/GeodatabaseCursorExtractor.java +++ b/server-extension-core/src/main/java/com/esri/serverextension/core/geodatabase/GeodatabaseCursorExtractor.java @@ -22,7 +22,7 @@ public interface GeodatabaseCursorExtractor { - T extractData(ICursor cursor, IField[] fields) throws IOException; + T extractData(ICursor cursor, GeodatabaseFieldMap fieldMap) throws IOException; - T extractData(IFeatureCursor featureCursor, IField[] fields) throws IOException; + T extractData(IFeatureCursor featureCursor, GeodatabaseFieldMap fieldMap) throws IOException; } diff --git a/server-extension-core/src/main/java/com/esri/serverextension/core/geodatabase/GeodatabaseFieldMap.java b/server-extension-core/src/main/java/com/esri/serverextension/core/geodatabase/GeodatabaseFieldMap.java new file mode 100644 index 0000000..dd6e6a2 --- /dev/null +++ b/server-extension-core/src/main/java/com/esri/serverextension/core/geodatabase/GeodatabaseFieldMap.java @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2017 Esri + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License.​ + */ + +package com.esri.serverextension.core.geodatabase; + +import com.esri.arcgis.geodatabase.IFeatureClass; +import com.esri.arcgis.geodatabase.IField; +import com.esri.arcgis.geodatabase.IFields; +import com.esri.arcgis.geodatabase.IQueryFilter; +import com.esri.serverextension.core.util.ArcObjectsInteropException; +import org.apache.commons.lang3.StringUtils; + +import java.io.IOException; +import java.util.*; + +public class GeodatabaseFieldMap { + + private Map fieldsIndicesByName; + + public GeodatabaseFieldMap() { + + } + + public Collection getFieldIndices() { + return Collections.unmodifiableCollection(fieldsIndicesByName.values()); + } + + public Set getFieldNames() { + return Collections.unmodifiableSet(fieldsIndicesByName.keySet()); + } + + public FieldIndex get(String fieldName) { + return fieldsIndicesByName.get(fieldName); + } + + public void initialize(IFields fields, String subFields) { + try { + String[] subFieldsArr = null; + if (StringUtils.isNotEmpty(subFields) || !"*".equals(subFields)) { + subFieldsArr = subFields.split(","); + } + int fieldCount = fields.getFieldCount(); + fieldsIndicesByName = new LinkedHashMap<>(); + for (int i = 0; i < fieldCount; i++) { + if (subFieldsArr != null) { + for (String subField : subFieldsArr) { + if (subField.equals(fields.getField(i).getName())) { + fieldsIndicesByName.put( + fields.getField(i).getName(), + new FieldIndex( + fields.getField(i), + i + ) + ); + break; + } + } + } else { + fieldsIndicesByName.put( + fields.getField(i).getName(), + new FieldIndex( + fields.getField(i), + i + ) + ); + } + } + } catch (IOException ex) { + throw new ArcObjectsInteropException("Failed to generate field index map."); + } + } + + public static class FieldIndex { + private IField field; + private int index; + + public FieldIndex(IField field, int index) { + this.field = field; + this.index = index; + } + + public IField getField() { + return field; + } + + public int getIndex() { + return index; + } + } +} diff --git a/server-extension-core/src/main/java/com/esri/serverextension/core/geodatabase/GeodatabaseObjectCallbackHandler.java b/server-extension-core/src/main/java/com/esri/serverextension/core/geodatabase/GeodatabaseObjectCallbackHandler.java index 1e7e4b1..400d4a3 100644 --- a/server-extension-core/src/main/java/com/esri/serverextension/core/geodatabase/GeodatabaseObjectCallbackHandler.java +++ b/server-extension-core/src/main/java/com/esri/serverextension/core/geodatabase/GeodatabaseObjectCallbackHandler.java @@ -22,7 +22,7 @@ public interface GeodatabaseObjectCallbackHandler { - public void setFields(IField[] fields) throws IOException; + public void setGeodatabaseFieldMap(GeodatabaseFieldMap fieldMap) throws IOException; public void processRow(IRow row) throws IOException; diff --git a/server-extension-core/src/main/java/com/esri/serverextension/core/geodatabase/GeodatabaseObjectCallbackHandlerCursorExtractor.java b/server-extension-core/src/main/java/com/esri/serverextension/core/geodatabase/GeodatabaseObjectCallbackHandlerCursorExtractor.java index 26e55e6..5fd57b0 100644 --- a/server-extension-core/src/main/java/com/esri/serverextension/core/geodatabase/GeodatabaseObjectCallbackHandlerCursorExtractor.java +++ b/server-extension-core/src/main/java/com/esri/serverextension/core/geodatabase/GeodatabaseObjectCallbackHandlerCursorExtractor.java @@ -43,12 +43,12 @@ public GeodatabaseObjectCallbackHandlerCursorExtractor(GeodatabaseObjectCallback } @Override - public Object extractData(ICursor cursor, IField[] fields) + public Object extractData(ICursor cursor, GeodatabaseFieldMap fieldMap) throws IOException { try { StopWatch stopWatch = StopWatch.createAndStart(); - objectCallbackHandler.setFields(fields); + objectCallbackHandler.setGeodatabaseFieldMap(fieldMap); IRow row = null; int rowCount = 0; @@ -90,11 +90,11 @@ public Object extractData(ICursor cursor, IField[] fields) } @Override - public Object extractData(IFeatureCursor featureCursor, IField[] fields) throws IOException { + public Object extractData(IFeatureCursor featureCursor, GeodatabaseFieldMap fieldMap) throws IOException { try { StopWatch stopWatch = StopWatch.createAndStart(); - objectCallbackHandler.setFields(fields); + objectCallbackHandler.setGeodatabaseFieldMap(fieldMap); IFeature feature = null; int featureCount = 0; diff --git a/server-extension-core/src/main/java/com/esri/serverextension/core/geodatabase/GeodatabaseObjectMapper.java b/server-extension-core/src/main/java/com/esri/serverextension/core/geodatabase/GeodatabaseObjectMapper.java index 8c86af1..4bd84d9 100644 --- a/server-extension-core/src/main/java/com/esri/serverextension/core/geodatabase/GeodatabaseObjectMapper.java +++ b/server-extension-core/src/main/java/com/esri/serverextension/core/geodatabase/GeodatabaseObjectMapper.java @@ -23,7 +23,7 @@ public interface GeodatabaseObjectMapper { - public void setFields(IField[] fields) throws IOException; + public void setGeodatabaseFieldMap(GeodatabaseFieldMap fieldMap) throws IOException; public T mapRow(IRow row) throws IOException; diff --git a/server-extension-core/src/main/java/com/esri/serverextension/core/geodatabase/GeodatabaseObjectMapperCursorExtractor.java b/server-extension-core/src/main/java/com/esri/serverextension/core/geodatabase/GeodatabaseObjectMapperCursorExtractor.java index 79a91fa..4e91d01 100644 --- a/server-extension-core/src/main/java/com/esri/serverextension/core/geodatabase/GeodatabaseObjectMapperCursorExtractor.java +++ b/server-extension-core/src/main/java/com/esri/serverextension/core/geodatabase/GeodatabaseObjectMapperCursorExtractor.java @@ -45,12 +45,12 @@ public GeodatabaseObjectMapperCursorExtractor(GeodatabaseObjectMapper objectM } @Override - public List extractData(ICursor cursor, IField[] fields) + public List extractData(ICursor cursor, GeodatabaseFieldMap fieldMap) throws IOException { try { StopWatch stopWatch = StopWatch.createAndStart(); - objectMapper.setFields(fields); + objectMapper.setGeodatabaseFieldMap(fieldMap); List resultSet = objectsExpected > 0 ? new ArrayList( objectsExpected) : new ArrayList(); @@ -94,11 +94,11 @@ public List extractData(ICursor cursor, IField[] fields) } @Override - public List extractData(IFeatureCursor featureCursor, IField[] fields) throws IOException { + public List extractData(IFeatureCursor featureCursor, GeodatabaseFieldMap fieldMap) throws IOException { try { StopWatch stopWatch = StopWatch.createAndStart(); - objectMapper.setFields(fields); + objectMapper.setGeodatabaseFieldMap(fieldMap); List resultSet = objectsExpected > 0 ? new ArrayList( objectsExpected) : new ArrayList(); diff --git a/server-extension-core/src/main/java/com/esri/serverextension/core/geodatabase/GeodatabaseTemplate.java b/server-extension-core/src/main/java/com/esri/serverextension/core/geodatabase/GeodatabaseTemplate.java index 040d6ab..716733b 100644 --- a/server-extension-core/src/main/java/com/esri/serverextension/core/geodatabase/GeodatabaseTemplate.java +++ b/server-extension-core/src/main/java/com/esri/serverextension/core/geodatabase/GeodatabaseTemplate.java @@ -19,6 +19,7 @@ import com.esri.arcgis.system.Cleaner; import com.esri.serverextension.core.util.StopWatch; import com.esri.serverextension.core.util.UncheckedIOException; +import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -60,8 +61,9 @@ public T query(IFeatureClass featureClass, IQueryFilter queryFilter, logger.debug("Preparing query took {} second(s).", stopWatch.stop() .elapsedTimeSeconds()); stopWatch.start(); - IField[] fields = toFieldsArray(cursor.getFields()); - result = cursorExtractor.extractData(cursor, fields); + GeodatabaseFieldMap fieldMap = new GeodatabaseFieldMap(); + fieldMap.initialize(cursor.getFields(), queryFilter != null ? queryFilter.getSubFields() : null); + result = cursorExtractor.extractData(cursor, fieldMap); logger.debug( "Executing query and extracting data from cursor took {} second(s).", stopWatch.stop().elapsedTimeSeconds()); @@ -74,14 +76,4 @@ public T query(IFeatureClass featureClass, IQueryFilter queryFilter, } return result; } - - private static final IField[] toFieldsArray(final IFields fields) - throws AutomationException, IOException { - int fieldCount = fields.getFieldCount(); - List fieldList = new ArrayList<>(fieldCount); - for (int i = 0; i < fieldCount; i++) { - fieldList.add(fields.getField(i)); - } - return fieldList.toArray(new IField[fieldCount]); - } } diff --git a/server-extension-core/src/main/java/com/esri/serverextension/core/rest/api/FeatureSet.java b/server-extension-core/src/main/java/com/esri/serverextension/core/rest/api/FeatureSet.java index abc731d..ae6f4e0 100644 --- a/server-extension-core/src/main/java/com/esri/serverextension/core/rest/api/FeatureSet.java +++ b/server-extension-core/src/main/java/com/esri/serverextension/core/rest/api/FeatureSet.java @@ -22,6 +22,7 @@ import javax.xml.bind.annotation.XmlRootElement; import java.io.Serializable; import java.util.List; +import java.util.Map; @XmlRootElement @JsonIgnoreProperties(ignoreUnknown = true) @@ -38,6 +39,7 @@ public class FeatureSet implements Serializable { private boolean hasZ; //optional Default is false. @JsonProperty("hasM") private boolean hasM; //optional Default is false. + private Map fieldAliases; private List fields; private List features; //features will include geometry for feature layers only @@ -101,6 +103,14 @@ public void setHasM(boolean hasM) { this.hasM = hasM; } + public Map getFieldAliases() { + return fieldAliases; + } + + public void setFieldAliases(Map fieldAliases) { + this.fieldAliases = fieldAliases; + } + public List getFields() { return fields; } From 6124f874819efeed4e336a62d598cc4fe7fd1e5e Mon Sep 17 00:00:00 2001 From: Carsten Piepel Date: Wed, 8 Mar 2017 00:43:39 -0800 Subject: [PATCH 11/13] Enables field selector in client app. --- examples/clustering-soe/client/index.html | 27 +---------------------- 1 file changed, 1 insertion(+), 26 deletions(-) diff --git a/examples/clustering-soe/client/index.html b/examples/clustering-soe/client/index.html index edb8361..7d55505 100644 --- a/examples/clustering-soe/client/index.html +++ b/examples/clustering-soe/client/index.html @@ -102,36 +102,17 @@ document.theMap = map; - - - - var home = new HomeButton({ map: map }, "HomeButton"); home.startup(); - - //var url = "https://sampleserver1.arcgisonline.com/ArcGIS/rest/services/PublicSafety/PublicSafetyFeedSample/MapServer/0"; - - //var url = "https://landfire.gov/arcgis/rest/services/Atlas/USGS_EDC_National_Atlas/MapServer/0"; - - //var url = "https://sampleserver1.arcgisonline.com/ArcGIS/rest/services/Demographics/ESRI_Census_USA/MapServer/5"; - - //census block points - //SET the url and the attribute fields here for now var url = "http://localhost:6080/arcgis/rest/services/Clustering/MapServer/exts/clustering/layers/0"; var clusterLayer = new ServerClusterLayer({url:url, id:"clusters"}); clusterLayer.clusterField = "Valuation"; - - - - - //add the legend - /* map.on("layers-add-result", function (results) { //add the legend @@ -146,16 +127,12 @@ }); */ - - - - map.addLayers([clusterLayer]); on(dom.byId("attributeW"), "change", function (e) { console.log(e); - map.clusterField = e.currentTarget.options[e.currentTarget.selectedIndex].value; + clusterLayer.clusterField = e.currentTarget.options[e.currentTarget.selectedIndex].value; clusterLayer.refreshLayer(); } ); @@ -186,8 +163,6 @@ - - + + + diff --git a/examples/clustering-soe/client/js/layer/ServerClusterLayer.js b/examples/clustering-soe/client/js/layer/ServerClusterLayer.js index f66b03f..f8071c0 100644 --- a/examples/clustering-soe/client/js/layer/ServerClusterLayer.js +++ b/examples/clustering-soe/client/js/layer/ServerClusterLayer.js @@ -303,7 +303,7 @@ define([ query.outSpatialreference = this._saveMap.spatialReference; query.geometry = this._saveMap.extent; query.clusterField = fieldName; - query.where = "Issue_Date >= date '2017-01-01 00:00:00'"; + query.where = this.where; var deferredQueryTask = queryTask.execute(query); diff --git a/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/Cluster.java b/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/Cluster.java index 3e2060f..dfd5fd6 100644 --- a/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/Cluster.java +++ b/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/Cluster.java @@ -14,72 +14,86 @@ package com.esri.serverextension.cluster; -import com.esri.serverextension.core.rest.api.Feature; - import java.util.ArrayList; -import java.util.List; /** * Created by kcoffin on 2/8/17. */ public class Cluster { - private double value; - private ClusterPoint point; - private List features; + private double _clusterCount; + private ClusterPoint _point; + private ArrayList _features; //A cluster needs to be created with at least one feature - public Cluster(Feature feature, double featureValue) { - features = new ArrayList<>(); - point = new ClusterPoint(feature.getGeometry()); - value = featureValue; - features.add(feature); - } - - public ClusterPoint getPoint() { - return point; + public Cluster(ClusterFeature feature){ + _features = new ArrayList<>(); + _point = new ClusterPoint(feature.getPoint()); + _clusterCount = feature.getValue(); + _features.add(feature); } - public double getValue() { - return value; + public ClusterPoint getPoint(){ + return _point; } - public void setValue(double ct) { - value = ct; + public void setClusterCount(double ct){ + _clusterCount = ct; } - public void addFeature(Feature feature, double featureValue) { - features.add(feature); - double ptc = featureValue / (value + featureValue); - double ctc = value / (value + featureValue); - ClusterPoint cluster = point; - ClusterPoint p = new ClusterPoint(feature.getGeometry()); + public void addFeature(ClusterFeature feature){ + double value = feature.getValue(); + double count = _clusterCount; + _features.add(feature); + double ptc = value/(count + value); + double ctc = count/(count + value); + ClusterPoint cluster = _point; + ClusterPoint p = feature.getPoint(); double x = (p.x * ptc + (cluster.x * ctc)); double y = (p.y * ptc + (cluster.y * ctc)); cluster.x = x; cluster.y = y; - this.value += featureValue; + _clusterCount += value; } - public void addPointCluster(Feature feature, double featureValue) { - double x, y; + public void addPointCluster(ClusterFeature feature, double ptCount){ + double count, x, y; + count = getValue(); - ClusterPoint p = new ClusterPoint(feature.getGeometry()); + ClusterPoint p = feature.getPoint(); getFeatures().add(feature); - double ptc = featureValue / (value + featureValue); - double ctc = value / (value + featureValue); + double ptc = ptCount/(count + ptCount); + double ctc = count/(count + ptCount); - x = (p.x * ptc + (point.x * ctc)); - y = (p.y * ptc + (point.y * ctc)); - value += featureValue; - point.x = x; - point.y = y; + x = (p.x * ptc + (_point.x * ctc)); + y = (p.y * ptc + (_point.y * ctc)); + _clusterCount += ptCount; + _point.x = x; + _point.y = y; } - public List getFeatures() { - return features; + public double getValue(){ + return _clusterCount; + } + + public ArrayList getFeatures(){ + return _features; + } + + + + + + public void print(){ + System.out.print("Cluster value:"+_clusterCount+" "); + _point.print(); + System.out.println(); + for(ClusterFeature f:_features){ + f.print(); + } + System.out.println("=============="); } } diff --git a/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/ClusterAssembler.java b/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/ClusterAssembler.java index ccaf5df..d00d6a8 100644 --- a/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/ClusterAssembler.java +++ b/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/ClusterAssembler.java @@ -14,207 +14,236 @@ package com.esri.serverextension.cluster; -import com.esri.serverextension.core.rest.api.Extent; -import com.esri.serverextension.core.rest.api.Feature; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; /** - * Assembles the cluster. - * - * Created by kcoffin on 2/8/17. - * + * Assembles the clustering */ public class ClusterAssembler { //The grid's extent - private Extent bbox; + private ClusterExtent _extent; + //cellsize of grid - private double cellSize; + private double _cellSize; - private double mapUnitsPerPixel; + private double _mapUnitsPerPixel; //number of columns in grid - private int numColumns; + private int _numColumns; //number of columns in grid - private int numRows; + private int _numRows; - //the cells which keeps the cluster info - private Map> cells; + + //the cells which keeps the clustering info + private Map> _cells; //the desired cluster distance in pixels - private double clusterDistanceInPixels; + private double _clusterDistanceInPixels; //All the clusters which are created - private List clusters; + private ArrayList _clusters; - // The field to determine a clusters weight - private String clusterFieldName; /** - * Creates a new cluster assembler. - * - * @param mapUnitsPerPixel map units per pixel (like meters per pixel) + * Assemble the clustering + * @param features all the features + * @param mapUnitsPerPixel map units per pixel (like meters per pixel) * @param clusterDistanceInPixels cluster distance in pixels - * @param bbox the extent in real world coordinates - * @param clusterFieldName the field to determine a clusters weight + * @param extent the extent in real world coordinates */ - public ClusterAssembler(double mapUnitsPerPixel, - double clusterDistanceInPixels, - Extent bbox, - String clusterFieldName) { - cells = new HashMap<>(); - this.mapUnitsPerPixel = mapUnitsPerPixel; - this.clusterDistanceInPixels = clusterDistanceInPixels; - cellSize = mapUnitsPerPixel * this.clusterDistanceInPixels; - this.bbox = bbox; - numColumns = getGridColumn(bbox.getXmax()) - getGridColumn(bbox.getXmin()) + 1; - numRows = getGridRow(bbox.getYmax()) - getGridRow(bbox.getYmin()) + 1; - clusters = new ArrayList<>(); - this.clusterFieldName = clusterFieldName; + public ClusterAssembler(ArrayList features, double mapUnitsPerPixel, + double clusterDistanceInPixels, ClusterExtent extent){ + addAllFeatures(features, mapUnitsPerPixel, clusterDistanceInPixels, extent); } /** - * Returns the assembled clusters. - * - * @return the assembled clusters + * Retrieves all the clusters + * @return */ - public List getClusters() { - return clusters; + public ArrayListgetClusters(){ + return _clusters; } - /** - * Builds the clusters. Call this method after all features have been added and before accessing the - * assembled clusters. - */ - public void buildClusters() { - for (Cluster cluster : clusters) { + + //Adds all the features. Called internally by the ctor + private void addAllFeatures(ArrayList features, double mapUnitsPerPixel, + double clusterDistanceInPixels, ClusterExtent extent + ){ + _cells = new HashMap<> (); + _mapUnitsPerPixel = mapUnitsPerPixel; + _clusterDistanceInPixels = clusterDistanceInPixels; + _cellSize = mapUnitsPerPixel * _clusterDistanceInPixels; + _extent = extent; + _numColumns = getGridColumn(extent.getXMax())+1; + _numRows = getGridRow(extent.getYMax())+1; + _clusters = new ArrayList<>(); + + //first sort the features based on the clusterfieldIndex + // Sorting by Lambda + Collections.sort(features, (ClusterFeature feature2, ClusterFeature feature1)-> + ((Double)feature1.getValue()).compareTo(feature2.getValue())); + + + /* JAVA 1.7 + Collections.sort(features, new Comparator() { + @Override + public int compare(ClusterFeature feature2, ClusterFeature feature1) + { + return ((Double)feature1.getValue()).compareTo((Double)feature2.getValue()); + } + }); + */ + + for (ClusterFeature feature:features){ + addFeature(feature); + } + + + + for (Cluster cluster:_clusters){ fixCluster(cluster); } - for (Cluster cluster : clusters) { + + + for (Cluster cluster:_clusters){ fixCluster(cluster); } + +/* + System.out.println("+++++++++++++++++++++++++++++++++++++"); + for (Cluster cluster:_clusters){ + examineCluster(cluster); + } +*/ +/* + for (Cluster cluster:_clusters){ + cluster.print(); + } +*/ + } + + + /** - * Adds a feature. - * + * Add a feature * @param feature */ - public void addFeature(Feature feature) { - Cluster closestCluster = getClosestCluster(new ClusterPoint(feature.getGeometry())); + private void addFeature(ClusterFeature feature) { + Cluster closestCluster = getClosestCluster(feature.getPoint()); if (closestCluster != null) { addFeatureToCluster(feature, closestCluster); - } else { + }else{ createCluster(feature);//create new cluster } } //from yValue real-world, what is the grid row - private int getGridRow(double yValue) { - return (int) Math.floor((yValue - bbox.getYmin()) / cellSize); + private int getGridRow(double yValue){ + return (int) Math.floor((yValue-_extent.getYMin())/_cellSize); } //from xValue real-world, what is the grid column - private int getGridColumn(double xValue) { - return (int) Math.floor((xValue - bbox.getXmin()) / cellSize); + private int getGridColumn(double xValue){ + return (int) Math.floor((xValue-_extent.getXMin())/_cellSize); } //add a feature to an EXISTING cluster - private void addFeatureToCluster(Feature feature, Cluster cluster) { + private void addFeatureToCluster(ClusterFeature feature, Cluster cluster) { //remove it from the grid because its coordinates are going to change removeClusterFromGrid(cluster); //add the feature to the cluster - cluster.addFeature(feature, getFeatureValue(feature)); + cluster.addFeature(feature); //add it back in to the grid addClusterToGrid(cluster); } //remove a cluster from the grid (it is already in the grid) - private void removeClusterFromGrid(Cluster cluster) { + private void removeClusterFromGrid(Cluster cluster){ ClusterPoint pt = cluster.getPoint(); int row = getGridRow(pt.y); int column = getGridColumn(pt.x); - int index = numRows * row + column; - ArrayList cell = cells.get(index); + int index = _numRows * row + column; + ArrayList cell = _cells.get(index); if (cell != null) { cell.remove(cluster); - } else { - throw new IllegalStateException("Unexpected cluster state."); + }else{ + System.out.println("Programming error"); } } //Add the cluster to the grid - private void addClusterToGrid(Cluster cluster) { + private void addClusterToGrid(Cluster cluster){ ClusterPoint pt = cluster.getPoint(); int row = getGridRow(pt.y); int column = getGridColumn(pt.x); - int index = numRows * row + column; - ArrayList cell = cells.get(index); + int index = _numRows * row + column; + ArrayList cell = _cells.get(index); if (cell == null) { cell = new ArrayList<>(); - cells.put(index, cell); + _cells.put(index, cell); } cell.add(cluster); } - private void createCluster(Feature feature) { - Cluster cluster = new Cluster(feature, getFeatureValue(feature)); + private void createCluster(ClusterFeature feature) { + Cluster cluster = new Cluster(feature); addClusterToGrid(cluster); - clusters.add(cluster); + _clusters.add(cluster); } + + /** * Gets the closest cluster within the cell distance - * * @param pt * @return */ - private Cluster getClosestCluster(ClusterPoint pt) { + public Cluster getClosestCluster(ClusterPoint pt){ int row = getGridRow(pt.y); int column = getGridColumn(pt.x); //should never happen as all features should come from within the extent - if (row < 0 || column < 0 || row >= numRows || column >= numColumns) { - throw new IllegalStateException("There's an error in the query"); + if (row < 0 || column < 0 || row>=_numRows || column>=_numColumns){ + System.out.println("There's an error in the query"); + return null; } int yStart = row; int yEnd = row; - if (row > 0) { - yStart = row - 1; + if (row > 0){ + yStart = row-1; } - if (row < numRows - 1) { - yEnd = row + 1; + if (row < _numRows-1){ + yEnd = row+1; } int xStart = column; int xEnd = column; - if (column > 0) { - xStart = column - 1; + if (column > 0){ + xStart = column-1; } - if (column < numColumns - 1) { - xEnd = column + 1; + if (column < _numColumns-1){ + xEnd = column+1; } /* - int xStart = (int)(Math.floor((extent.xmin - this._xmin) / this.cellSize)); - int xEnd = (int)(Math.floor((extent.xmax - this._xmin) / this.cellSize)); - int yStart = (int)(Math.floor((extent.ymin - this._ymin) / this.cellSize)); - int yEnd = (int)(Math.floor((extent.ymax - this._ymin) / this.cellSize)); + int xStart = (int)(Math.floor((extent.xmin - this._xmin) / this._cellSize)); + int xEnd = (int)(Math.floor((extent.xmax - this._xmin) / this._cellSize)); + int yStart = (int)(Math.floor((extent.ymin - this._ymin) / this._cellSize)); + int yEnd = (int)(Math.floor((extent.ymax - this._ymin) / this._cellSize)); */ Cluster minCluster = null; @@ -222,8 +251,8 @@ private Cluster getClosestCluster(ClusterPoint pt) { for (int x = xStart; x <= xEnd; x++) { for (int y = yStart; y <= yEnd; y++) { - int index = numRows * y + x; - ArrayList cell = cells.get(index); + int index = _numRows * y + x; + ArrayList cell = _cells.get(index); if (cell != null) { for (int i = 0; i < cell.size(); i++) { Cluster cluster = cell.get(i); @@ -236,62 +265,69 @@ private Cluster getClosestCluster(ClusterPoint pt) { } } } - if (minDis2 > 0) { + if (minDis2 > 0){ minDis2 = Math.sqrt(minDis2); } - if (minDis2 > cellSize) { + if (minDis2 > _cellSize){ return null; } return minCluster; } + + //This examines a cluster and determines if all features in it are the closest to it. - private void examineCluster(Cluster cluster) { - List features = cluster.getFeatures(); - for (int k = 0; k < features.size(); k++) { - Feature feature = features.get(k); - ClusterPoint p = new ClusterPoint(feature.getGeometry()); + public void examineCluster(Cluster cluster){ + + + ArrayList features = cluster.getFeatures(); + for (int k=0;k clusterFeatures = new ArrayList<>(); - public ClusterAssemblerCallbackHandler(ClusterAssembler clusterAssembler) { - this.clusterAssembler = clusterAssembler; + public ClusterAssemblerCallbackHandler(String clusterFieldName) { + this.clusterFieldName = clusterFieldName; } public int getFeatureCount() { return featureCount; } + public ArrayList getClusterFeatures() { + return clusterFeatures; + } + @Override public void setGeodatabaseFieldMap(GeodatabaseFieldMap fieldMap) throws IOException { this.fieldMap = fieldMap; + clusterFieldIndex = fieldMap.get(clusterFieldName).getIndex(); } @Override public void processRow(IRow row) throws IOException { - throw new UnsupportedOperationException("This class only handles features."); + throw new UnsupportedOperationException("This callback handler only supports features."); } @Override @@ -56,9 +68,19 @@ public void processFeature(IFeature feature) throws IOException { for (GeodatabaseFieldMap.FieldIndex fieldIndex : fieldMap.getFieldIndices()) { attributes.put(fieldIndex.getField().getName(), feature.getValue(fieldIndex.getIndex())); } - Feature f = new Feature(); - f.setGeometry(feature.getShape()); - f.setAttributes(attributes); - clusterAssembler.addFeature(f); + IGeometry geometry = feature.getShape(); + if (geometry instanceof IPoint && !geometry.isEmpty()) { + IPoint point = (IPoint)geometry; + ClusterPoint clusterPoint = new ClusterPoint(point.getX(), point.getY()); + Object value = feature.getValue(clusterFieldIndex); + if (value == null) { + return; + } + if (value instanceof Number) { + ClusterFeature clusterFeature = new ClusterFeature(clusterPoint, + ((Number) value).doubleValue()); + clusterFeatures.add(clusterFeature); + } + } } } diff --git a/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/ClusterExtent.java b/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/ClusterExtent.java new file mode 100644 index 0000000..a4ba376 --- /dev/null +++ b/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/ClusterExtent.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2017 Esri + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License.​ + */ + +package com.esri.serverextension.cluster; + +/** + * Immutable ClusterExtent + */ +public class ClusterExtent { + private double _xmin; + private double _ymin; + private double _xmax; + private double _ymax; + + /** + * Construct an extent. This is where everything gets assigned + * @param xmin + * @param ymin + * @param xmax + * @param ymax + */ + public ClusterExtent(double xmin, double ymin, double xmax, double ymax){ + _xmin = xmin; + _ymin = ymin; + _xmax = xmax; + _ymax = ymax; + } + + public double getWidth(){ + return _xmax - _xmin; + } + public double getHeight(){ + return _ymax - _ymin; + } + public double getXMin(){ + return _xmin; + } + public double getYMin(){ + return _ymin; + } + public double getXMax(){ + return _xmax; + } + public double getYMax(){ + return _ymax; + } +} diff --git a/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/ClusterFeature.java b/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/ClusterFeature.java new file mode 100644 index 0000000..d3ff6e3 --- /dev/null +++ b/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/ClusterFeature.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2017 Esri + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License.​ + */ + +package com.esri.serverextension.cluster; + +/** + * Created by kcoffin on 2/8/17. + */ +public class ClusterFeature { + + private double _value; + private ClusterPoint _point; + + public ClusterFeature(ClusterPoint point, double value){ + _point = point; + _value = value; + } + + public double getValue(){ + return _value; + } + + + public ClusterPoint getPoint(){ + return _point; + } + + public void print(){ + System.out.print("ClusterFeature value:"+_value+" "); + _point.print(); + System.out.println(); + } +} diff --git a/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/ClusterLayerResource.java b/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/ClusterLayerResource.java index 7f9bd9c..5e09085 100644 --- a/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/ClusterLayerResource.java +++ b/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/ClusterLayerResource.java @@ -65,15 +65,22 @@ public FeatureSet query(@PathVariable("layerId") int layerId, @BeanParam Cluster try { IFeatureClass featureClass = MapServerUtilities.getPointFeatureClassByLayerID(layerId, serverContext); IQueryFilter queryFilter = getQueryFilter(input, featureClass.getShapeFieldName()); + ClusterAssemblerCallbackHandler clusterAssemblerCallbackHandler = new ClusterAssemblerCallbackHandler(input.getClusterField()); GeodatabaseTemplate geodatabaseTemplate = new GeodatabaseTemplate(); + geodatabaseTemplate.query(featureClass, queryFilter, clusterAssemblerCallbackHandler); + ClusterExtent clusterExtent = new ClusterExtent( + input.getBbox().getXmin(), + input.getBbox().getYmin(), + input.getBbox().getXmax(), + input.getBbox().getYmax() + ); ClusterAssembler clusterAssembler = new ClusterAssembler( + clusterAssemblerCallbackHandler.getClusterFeatures(), input.getMapUnitsPerPixel(), input.getClusterDistanceInPixels(), - input.getBbox(), - input.getClusterField() + clusterExtent ); - ClusterAssemblerCallbackHandler clusterAssemblerCallbackHandler = new ClusterAssemblerCallbackHandler(clusterAssembler); - geodatabaseTemplate.query(featureClass, queryFilter, clusterAssemblerCallbackHandler); + FeatureSet featureSet = new FeatureSet(); featureSet.setDisplayFieldName(input.getClusterField()); Field field = new Field(input.getClusterField(), @@ -83,7 +90,6 @@ public FeatureSet query(@PathVariable("layerId") int layerId, @BeanParam Cluster featureSet.setFields(fields); featureSet.setSpatialReference(getOutSpatialReference(input, serverContext)); featureSet.setGeometryType(GeometryType.esriGeometryPoint); - clusterAssembler.buildClusters(); List clusters = clusterAssembler.getClusters(); if (!CollectionUtils.isEmpty(clusters)) { List features = new ArrayList<>(clusterAssembler.getClusters().size()); @@ -103,14 +109,6 @@ public FeatureSet query(@PathVariable("layerId") int layerId, @BeanParam Cluster features.add(clusterFeature); } featureSet.setFeatures(features); - } else { - List features = new ArrayList<>(1); - Feature clusterFeature = new Feature(); - Map attributes = new LinkedHashMap<>(); - attributes.put(input.getClusterField().intern(), clusterAssemblerCallbackHandler.getFeatureCount()); - clusterFeature.setAttributes(attributes); - features.add(clusterFeature); - featureSet.setFeatures(features); } return featureSet; } catch (IOException ex) { diff --git a/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/ClusterPoint.java b/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/ClusterPoint.java index 7a663b5..0f59bd9 100644 --- a/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/ClusterPoint.java +++ b/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/ClusterPoint.java @@ -14,65 +14,34 @@ package com.esri.serverextension.cluster; -import com.esri.arcgis.geometry.IGeometry; -import com.esri.arcgis.geometry.IPoint; -import com.esri.arcgis.geometry.Point; -import com.esri.serverextension.core.util.ArcObjectsInteropException; - -import java.io.IOException; - /** * Created by kcoffin on 2/8/17. */ public class ClusterPoint { public double x; public double y; - - public ClusterPoint(double x, double y) { + public ClusterPoint(double x, double y){ this.x = x; this.y = y; } - - public ClusterPoint(ClusterPoint pt) { + public ClusterPoint(ClusterPoint pt){ x = pt.x; y = pt.y; } - public ClusterPoint(IGeometry geometry) { - if (geometry == null) { - throw new NullPointerException("Argument 'geometry' must not be null."); - } - if (geometry instanceof IPoint) { - IPoint pt = (IPoint)geometry; - try { - x = pt.getX(); - y = pt.getY(); - } catch (IOException ex) { - throw new ArcObjectsInteropException("Failed to create cluster point from IPoint."); - } - } else { - throw new IllegalArgumentException(String.format("Geometry is not a point: %1$s", geometry.getClass().getName())); - } - } - - public double squareDistance(ClusterPoint pt) { + public double squareDistance(ClusterPoint pt){ double dx = pt.x - x; double dy = pt.y - y; - return (dx * dx) + (dy * dy); + return (dx*dx) + (dy*dy); } - - public double distance(ClusterPoint pt) { + public double distance(ClusterPoint pt){ return Math.sqrt(squareDistance(pt)); } - public IPoint getPoint() { - try { - Point pt = new Point(); - pt.setX(x); - pt.setY(y); - return pt; - } catch (IOException ex) { - throw new ArcObjectsInteropException("Failed to create point.", ex); - } + + + public void print(){ + System.out.print("("+x+","+y+")"); + } } diff --git a/examples/clustering-soe/src/test/java/com/esri/serverextension/cluster/ClusterAssemblerIT.java b/examples/clustering-soe/src/test/java/com/esri/serverextension/cluster/ClusterAssemblerIT.java index 38a34a0..31afd1c 100644 --- a/examples/clustering-soe/src/test/java/com/esri/serverextension/cluster/ClusterAssemblerIT.java +++ b/examples/clustering-soe/src/test/java/com/esri/serverextension/cluster/ClusterAssemblerIT.java @@ -18,10 +18,10 @@ import com.esri.arcgis.geometry.IEnvelope; import com.esri.serverextension.core.geodatabase.FileGDBWorkspaceFactoryBean; import com.esri.serverextension.core.geodatabase.GeodatabaseTemplate; -import com.esri.serverextension.core.rest.api.Extent; import com.esri.serverextension.core.rest.json.JSONGeometryMapper; import com.esri.serverextension.core.util.ArcObjectsInitializer; import com.esri.serverextension.core.util.ArcObjectsUtilities; +import com.esri.serverextension.core.util.StopWatch; import com.esri.serverextension.test.AbstractArcObjectsIT; import net.jcip.annotations.NotThreadSafe; import org.junit.Test; @@ -42,44 +42,57 @@ public class ClusterAssemblerIT extends AbstractArcObjectsIT { private IWorkspace workspace; @Test - public void testClusterAssembler() throws IOException { - Extent bbox = new Extent(); - bbox.setXmin(-13244092.36900171); - bbox.setYmin(4000883.3498998554); - bbox.setXmax(-13118812.079642477); - bbox.setYmax(4061574.350358204); - ClusterAssembler clusterAssembler = new ClusterAssembler(76.43702828507277, - 100, bbox, "Valuation"); + public void testClusterAssembler(String where) throws IOException { + System.out.println("1. Setting up query filter."); IFeatureClass featureClass = ((IFeatureWorkspace)workspace).openFeatureClass("Permit_Features"); SpatialFilter spatialFilter = new SpatialFilter(); spatialFilter.setSubFields("Valuation,Shape"); spatialFilter.setGeometryField("Shape"); spatialFilter.setSpatialRel(esriSpatialRelEnum.esriSpatialRelIntersects); - JSONGeometryMapper geometryMapper = new JSONGeometryMapper(); - IEnvelope envelope = geometryMapper.readEnvelope("{\"xmin\":-13244092.36900171,\"ymin\":4000883.3498998554,\"xmax\":-13118812.079642477,\"ymax\":4061574.350358204,\"spatialReference\":{\"wkid\":102100}}"); - + IEnvelope envelope = geometryMapper.readEnvelope("{\"xmin\":-13244092.36900171," + + "\"ymin\":4000883.3498998554," + + "\"xmax\":-13118812.079642477," + + "\"ymax\":4061574.350358204," + + "\"spatialReference\":{\"wkid\":102100}}"); spatialFilter.setGeometryByRef(envelope); - spatialFilter.setWhereClause("Issue_Date >= date '2017-01-01 00:00:00'"); - //spatialFilter.setWhereClause("OBJECTID < 10"); + spatialFilter.setWhereClause(where); spatialFilter.setOutputSpatialReferenceByRef("Shape", ArcObjectsUtilities.createSpatialReference(102100)); + + System.out.println("2. Executing query."); GeodatabaseTemplate geodatabaseTemplate = new GeodatabaseTemplate(); - geodatabaseTemplate.query(featureClass, spatialFilter, new ClusterAssemblerCallbackHandler(clusterAssembler)); + ClusterAssemblerCallbackHandler clusterAssemblerCallbackHandler = new ClusterAssemblerCallbackHandler("Valuation"); + geodatabaseTemplate.query(featureClass, spatialFilter, clusterAssemblerCallbackHandler); + System.out.println(String.format("# of input features: %1$d", clusterAssemblerCallbackHandler.getFeatureCount())); + + System.out.println("3. Building clusters."); + ClusterExtent clusterExtent = new ClusterExtent(-13244092.36900171, + 4000883.3498998554, + -13118812.079642477, + 4061574.350358204); + ClusterAssembler clusterAssembler = new ClusterAssembler( + clusterAssemblerCallbackHandler.getClusterFeatures(), + 76.43702828507277, + 100, + clusterExtent); List clusters = clusterAssembler.getClusters(); int clusterCount = 0; for (Cluster cluster : clusters) { - System.out.println(String.format("Cluster %1$d: (x: %2$f y: %3$f), %4$f", ++clusterCount, cluster.getPoint().x, cluster.getPoint().y, cluster.getValue())); + System.out.println(String.format("Cluster %1$d: (x: %2$f y: %3$f), %4$f", ++clusterCount, + cluster.getPoint().x, cluster.getPoint().y, cluster.getValue())); } } public static void main(String[] args) throws Exception { + StopWatch timer = StopWatch.createAndStart(); ArcObjectsInitializer.getInstance().init(); FileGDBWorkspaceFactoryBean fileGDBWorkspaceFactoryBean = new FileGDBWorkspaceFactoryBean(); fileGDBWorkspaceFactoryBean.setDatabase("D:\\Development\\Projects\\sever-extension-java\\examples\\clustering-soe\\data\\Clustering\\Clustering.gdb"); IWorkspace workspace = fileGDBWorkspaceFactoryBean.getObject(); ClusterAssemblerIT clusterAssemblerIT = new ClusterAssemblerIT(); clusterAssemblerIT.workspace = workspace; - clusterAssemblerIT.testClusterAssembler(); + clusterAssemblerIT.testClusterAssembler("Issue_Date >= date '2017-01-01 00:00:00'"); ArcObjectsInitializer.getInstance().shutdown(); + System.out.println(String.format("Time elapsed: %1$f", timer.stop().elapsedTimeSeconds())); } }