${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/client/index.html b/examples/clustering-soe/client/index.html
new file mode 100644
index 0000000..4533c83
--- /dev/null
+++ b/examples/clustering-soe/client/index.html
@@ -0,0 +1,200 @@
+
+
+
+
+
+ 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..f8071c0
--- /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
+
+
+ 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
+ ${project.parent.version}
+
+
+
+
+
+
+ src/main/resources
+ true
+
+ **/*.properties
+
+
+
+
+
+ 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
+
+
+
+
+
+ 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
new file mode 100644
index 0000000..434b784
--- /dev/null
+++ b/examples/clustering-soe/src/assembly/soe-assembly.xml
@@ -0,0 +1,40 @@
+
+
+
+ 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..e9942cc
--- /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
+
+
+ clustering
+ Clustering
+ ${project.description}
+
+
+
+
+ false
+ 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..880ec16
--- /dev/null
+++ b/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/AboutResource.java
@@ -0,0 +1,71 @@
+/*
+ * 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.server.json.JSONObject;
+import org.springframework.stereotype.Service;
+import org.springframework.web.bind.annotation.RequestMapping;
+
+import javax.annotation.Resource;
+
+@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
new file mode 100644
index 0000000..dfd5fd6
--- /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 ClusterPoint _point;
+ private ArrayList _features;
+
+ //A cluster needs to be created with at least one feature
+ public Cluster(ClusterFeature feature){
+ _features = new ArrayList<>();
+ _point = new ClusterPoint(feature.getPoint());
+ _clusterCount = feature.getValue();
+ _features.add(feature);
+ }
+
+ public ClusterPoint getPoint(){
+ return _point;
+ }
+
+ public void setClusterCount(double ct){
+ _clusterCount = ct;
+ }
+
+ 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;
+ _clusterCount += value;
+ }
+
+ public void addPointCluster(ClusterFeature feature, double ptCount){
+ double count, x, y;
+ count = getValue();
+
+ ClusterPoint 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(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
new file mode 100644
index 0000000..d00d6a8
--- /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 clustering
+ */
+public class ClusterAssembler {
+
+ //The grid's extent
+ private ClusterExtent _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 clustering info
+ private Map> _cells;
+
+ //the desired cluster distance in pixels
+ private double _clusterDistanceInPixels;
+
+ //All the clusters which are created
+ private ArrayList _clusters;
+
+
+ /**
+ * 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 extent the extent in real world coordinates
+ */
+ public ClusterAssembler(ArrayList features, double mapUnitsPerPixel,
+ double clusterDistanceInPixels, ClusterExtent 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, 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){
+ 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(ClusterFeature 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(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);
+
+ //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){
+ ClusterPoint 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){
+ ClusterPoint 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(ClusterFeature 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(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){
+ 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;k clusterFeatures = new ArrayList<>();
+
+ 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 callback handler only supports features.");
+ }
+
+ @Override
+ public void processFeature(IFeature feature) throws IOException {
+ featureCount++;
+ Map attributes = new LinkedHashMap<>();
+ for (GeodatabaseFieldMap.FieldIndex fieldIndex : fieldMap.getFieldIndices()) {
+ attributes.put(fieldIndex.getField().getName(), feature.getValue(fieldIndex.getIndex()));
+ }
+ 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
new file mode 100644
index 0000000..5e09085
--- /dev/null
+++ b/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/ClusterLayerResource.java
@@ -0,0 +1,168 @@
+/*
+ * 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.*;
+import com.esri.serverextension.core.rest.api.Feature;
+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());
+ 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(),
+ clusterExtent
+ );
+
+ 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));
+ featureSet.setGeometryType(GeometryType.esriGeometryPoint);
+ 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();
+ 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);
+ }
+ 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 {
+ IQueryFilter2 queryFilter = null;
+ if (input.getGeometry() != null) {
+ SpatialFilter 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()
+ ));
+ }
+ queryFilter.setSpatialResolution(100000.0d);
+ 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/ClusterLayersResource.java b/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/ClusterLayersResource.java
new file mode 100644
index 0000000..98b3264
--- /dev/null
+++ b/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/ClusterLayersResource.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 ClusterLayersResource {
+
+ @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/ClusterPoint.java b/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/ClusterPoint.java
new file mode 100644
index 0000000..0f59bd9
--- /dev/null
+++ b/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/ClusterPoint.java
@@ -0,0 +1,47 @@
+/*
+ * 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 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 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 void print(){
+ System.out.print("("+x+","+y+")");
+
+ }
+}
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/ClusteringConfig.java b/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/ClusteringConfig.java
new file mode 100644
index 0000000..1b20713
--- /dev/null
+++ b/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/ClusteringConfig.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.cluster;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.PropertySource;
+import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
+import org.springframework.core.env.Environment;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+@Configuration
+@ComponentScan("com.esri.serverextension.cluster")
+@PropertySource(value = { "classpath:/buildInfo.properties", "classpath:/git.properties" })
+public class ClusteringConfig {
+
+ @Value("${project.name}")
+ public String projectName;
+ @Value("${project.description}")
+ public String projectDescription;
+ @Value("${project.version}")
+ public String projectVersion;
+ @Value("${git.branch}")
+ public String gitBranch;
+ @Value("${git.commit.id}")
+ public String gitCommitID;
+ @Inject
+ private Environment env;
+
+ public ClusteringConfig() {
+ }
+
+ @Bean
+ public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
+ return new PropertySourcesPlaceholderConfigurer();
+ }
+
+ @Bean
+ @Singleton
+ public String projectName() {
+ return projectName;
+ }
+
+ @Bean
+ @Singleton
+ public String projectDescription() {
+ return projectDescription;
+ }
+
+ @Bean
+ @Singleton
+ public String projectVersion() {
+ return projectVersion;
+ }
+
+ @Bean
+ @Singleton
+ public String gitBranch() {
+ return gitBranch;
+ }
+
+ @Bean
+ @Singleton
+ public String gitCommitID() {
+ return gitCommitID;
+ }
+}
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
new file mode 100644
index 0000000..6640dba
--- /dev/null
+++ b/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/ClusteringExtension.java
@@ -0,0 +1,80 @@
+/*
+ * 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.interop.AutomationException;
+import com.esri.arcgis.interop.extn.ArcGISExtension;
+import com.esri.arcgis.interop.extn.ServerObjectExtProperties;
+import com.esri.arcgis.server.json.JSONArray;
+import com.esri.arcgis.server.json.JSONObject;
+import com.esri.arcgis.system.ServerUtilities;
+import com.esri.serverextension.core.server.AbstractRestServerObjectExtension;
+import org.springframework.context.annotation.AnnotationConfigApplicationContext;
+
+import java.io.IOException;
+
+@ArcGISExtension
+@ServerObjectExtProperties(displayName = "Clustering",
+ description = "Provides server-side clustering of point features.",
+ interceptor = true,
+ servicetype = "MapService")
+public class ClusteringExtension extends AbstractRestServerObjectExtension {
+
+ 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(
+ AnnotationConfigApplicationContext applicationContext) {
+ super.doConfigure(applicationContext);
+ applicationContext.register(ClusteringConfig.class);
+ }
+
+ @Override
+ protected void doShutdown() {
+ super.doShutdown();
+ }
+
+ @Override
+ public String getSchema() throws IOException, AutomationException {
+ ServerObjectExtProperties annotation = this.getClass().getAnnotation(
+ ServerObjectExtProperties.class);
+ JSONObject rootResource = ServerUtilities.createResource(
+ annotation.displayName(), annotation.description(), false,
+ false);
+
+ JSONArray resources = new JSONArray();
+ rootResource.put("resources", resources);
+
+ JSONArray layerResourceOperations = new JSONArray();
+
+ JSONObject layerQueryOperation = ServerUtilities.createOperation(
+ "query", QUERY_OPERATION_PARAMETER_NAMES, "json", false);
+ layerResourceOperations.put(layerQueryOperation);
+
+ JSONObject layersResource = ServerUtilities.createResource("layers",
+ "Cluster layers", true, true);
+ layersResource.put("operations", layerResourceOperations);
+ resources.put(layersResource);
+
+ JSONObject about = ServerUtilities.createResource("about",
+ "About Clustering", false, false);
+ resources.put(about);
+
+ return rootResource.toString();
+ }
+}
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
new file mode 100644
index 0000000..0a7cad0
--- /dev/null
+++ b/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/MapServerUtilities.java
@@ -0,0 +1,114 @@
+/*
+ * 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.*;
+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;
+import com.esri.serverextension.core.util.ArcObjectsInteropException;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+public class MapServerUtilities {
+
+ public static final List 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.");
+ }
+ }
+
+ 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));
+ }
+
+ 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/RootResource.java b/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/RootResource.java
new file mode 100644
index 0000000..f540757
--- /dev/null
+++ b/examples/clustering-soe/src/main/java/com/esri/serverextension/cluster/RootResource.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;
+
+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.Service;
+import org.springframework.web.bind.annotation.RequestMapping;
+
+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();
+ 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.");
+ }
+ }
+ rootResource.put("layers", layersArray);
+ 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/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..31afd1c
--- /dev/null
+++ b/examples/clustering-soe/src/test/java/com/esri/serverextension/cluster/ClusterAssemblerIT.java
@@ -0,0 +1,98 @@
+/*
+ * 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.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;
+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(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}}");
+ spatialFilter.setGeometryByRef(envelope);
+ spatialFilter.setWhereClause(where);
+ spatialFilter.setOutputSpatialReferenceByRef("Shape", ArcObjectsUtilities.createSpatialReference(102100));
+
+ System.out.println("2. Executing query.");
+ GeodatabaseTemplate geodatabaseTemplate = new GeodatabaseTemplate();
+ 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()));
+ }
+ }
+
+ 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("Issue_Date >= date '2017-01-01 00:00:00'");
+ ArcObjectsInitializer.getInstance().shutdown();
+ System.out.println(String.format("Time elapsed: %1$f", timer.stop().elapsedTimeSeconds()));
+ }
+}
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 997b1cb..8283c61 100644
--- a/pom.xml
+++ b/pom.xml
@@ -23,8 +23,9 @@
pom
server-extension-core
- examples/attribute-security-filter-soi
server-extension-template
+ examples/attribute-security-filter-soi
+ examples/clustering-soe
1.8
@@ -32,7 +33,6 @@
4.3.6.RELEASE
UTF-8
UTF-8
- **/*IT.java
${env.JAVA_HOME}
@@ -57,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
+
@@ -99,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 c46d920..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
@@ -67,6 +52,10 @@
+
+ org.springframework
+ spring-jdbc
+
javax.enterprise
cdi-api
@@ -75,17 +64,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
@@ -99,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
new file mode 100644
index 0000000..ca29564
--- /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, GeodatabaseFieldMap fieldMap) 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
new file mode 100644
index 0000000..400d4a3
--- /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 setGeodatabaseFieldMap(GeodatabaseFieldMap fieldMap) 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..5fd57b0
--- /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