├── .gitignore ├── LICENSE ├── README.md ├── media ├── SOI_001.png ├── SOI_002.png ├── addext.png ├── capabilities.png ├── javaconfigtool.png └── webmap.png ├── pom.xml └── src ├── assembly ├── soe-assembly.xml └── soe-config.xml └── main ├── java └── com │ └── esri │ ├── AbstractSOI.java │ ├── ColorMapper.java │ ├── HexCell.java │ └── ServerUtilities2.java ├── resources └── log4j.properties └── scala └── com └── esri ├── ExportImageSOI.scala └── ScaleLoc.scala /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | target/ 3 | .idea/ 4 | dependency-reduced-pom.xml 5 | 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2015 Mansour Raad 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ArcGIS Server Object Interceptor and MemSQL 2 | 3 | Server Object Interceptors (SOI) have the same API as [Server Object Extensions](http://resources.arcgis.com/en/help/main/10.1/index.html#//0154000004s5000000) (SOE), and 4 | are intended to extend an ArcGIS Server with custom capabilities. 5 | An SOI intercepts REST and/or SOAP calls on a Map Service before and/or after it executes an operation on an SOE or Server Object (SO). 6 | Think [servlet filters](http://www.oracle.com/technetwork/java/filters-137243.html). 7 | 8 | ![](media/SOI_001.png) 9 | 10 | A use case of an interceptor is to manipulate the visibility of layers or data fields based on the user credentials in single-sign-on based request. 11 | 12 | Another use case of an SOI associated with a published MXD is to intercept an export image operation and digitally watermark the original resulting image for copyright purposes. 13 | 14 | Whenever a pan or a zoom occurs in WebMap with a layer referencing a Dynamic MapService, it is internally invoking an export image operation with an optional set of parameters: 15 | 16 | * An output image size in pixels 17 | * A current map extent in typically geographic degrees or web mercator meters values 18 | * A "where" clause with constraining feature attribute values 19 | 20 | The default implementation performs a spatial query on the registered data source constraining the output features by the 21 | supplied map extent and the "where" clause. It draws the resulting set of features on an off-screen image and returns that image to the caller. 22 | 23 | In this project, the SOI implementation intercepts the export image operation, draws the features on an off-screen image. However the query of the features is performed on an "external" data source. 24 | 25 | That external data source is [MemSQL](http://www.memsql.com/) with its newly enhanced [geospatial capabilities](http://blog.memsql.com/geospatial-intelligence/). 26 | A client that implements the [MySQL wire protocol](http://dev.mysql.com/doc/internals/en/client-server-protocol.html) can interact with a MemSQL server and execute spatial SQL. 27 | Mind you that this is not an implementation of the [OpenGIS Simple Features Specification](http://en.wikipedia.org/wiki/Simple_Features), but has enough spatial functions for this SOI. 28 | 29 | ![](media/SOI_002.png) 30 | 31 | ## Building The Project 32 | 33 | An SOI can be implemented in .NET or Java. This implementation is based on [Scala](http://www.scala-lang.org/) (because I can :-) 34 | 35 | Before proceeding, make sure that the ArcGIS JVM is configured with adequate heap space using the ArcGIS Java Configuration Tool: 36 | 37 | ![](media/javaconfigtool.png) 38 | 39 | An SOI is packaged inside an soe file. An soe file is a [zipped](http://en.wikipedia.org/wiki/Zip_%28file_format%29) folder 40 | that contains a `Config.xml` file and an `Install` folder containing all the runtime jar dependencies. 41 | The `Config.xml` enumerates the SOE/I and is the place holder for the SOE/I display name, description, entry point class name and custom properties. 42 | The automation of the soe file generation is done using [Maven](http://maven.apache.org/) in this project. 43 | I would like to thank my coworker Carsten P. for the boost into the realm of SOIs and for graciously sharing the initial [pom.xml](http://maven.apache.org/pom.html). 44 | 45 | **Note**: Before the initial build, Locate the `arcobjects.jar` file in your ArcGIS installation (on my machine, It was in `C:\Program Files\ArcGIS\Server\framework\lib`) and add it to your local maven repository using: 46 | 47 | ``` 48 | mvn install:install-file\ 49 | -Dfile="arcobjects.jar"\ 50 | -DgroupId=com.esri\ 51 | -DartifactId=arcobjects\ 52 | -Dversion=10.3.1\ 53 | -Dpackaging=jar\ 54 | -DgeneratePom=true 55 | ``` 56 | 57 | Build the project using: 58 | 59 | ``` 60 | mvn clean package 61 | ``` 62 | 63 | This will create a file named `ExportImageSOI-XXX.soe` in the `target` folder. 64 | 65 | ## Adding The Extension 66 | 67 | Add the extension to the site using the ArcGIS Server Manager: 68 | 69 | ![](media/addext.png) 70 | 71 | ## Adding Capabilities 72 | 73 | An SOI is associated with a publish MapService. Since we are intercepting the export image request, we just need a "stand-in" MapService. 74 | In my case, I created a simple feature class with one feature at (0,0) and published it as the stand-in MapService onto which I enabled the SOI capabilities. 75 | 76 | ![](media/capabilities.png) 77 | 78 | Note that when you select the SOI, you have the option to configure its runtime properties. 79 | These are configured with default values in the `Config.xml` file. The latter is generated by `Maven` during packaging using the `src/assembly/soe-config.xml` file as a template. 80 | 81 | ## Troubleshooting 82 | 83 | Sometime during rapid development and deployment, the underlying COM caching in ArcGIS gets....not sure exactly what word to use, so I'm going to say...confused ! 84 | And that results in the famous-but-useless error code "0x99999 - Unspecified Error" message. God bless the heart of the COM core developers. 85 | 86 | The best way I found out to [keep calm and carry on](http://en.wikipedia.org/wiki/Keep_Calm_and_Carry_On) is to do the following: 87 | 88 | * Stop the ArcGIS Server using the `Services` application. 89 | * Navigate to the hidden `C:\Users\arcgis\AppData\Local\ESRI\Server10.3\AssemblyCache` folder 90 | * Delete all the folders with GUID as names 91 | * Start the ArcGIS Server 92 | 93 | ## Implementation Details 94 | 95 | The `ExportImageSOI.scala` extends the `AbstractSOI.java` class that contains the boilerplate code to becoming an interceptor instance. 96 | That means implementing the `IServerObjectExtension`, `IRESTRequestHandler`, `IWebRequestHandler`, `IRequestHandler2` interfaces as default handlers. 97 | `ExportImageSOI` implements the `IObjectConstruct` interface which enables us to access the runtime properties whose default values are in the `Config.xml`. 98 | In this SOI implementation, a reference to the [JDBC MySQL driver](http://dev.mysql.com/downloads/connector/j/) is invoked forcing it to load to enable the connection to a MemSQL AWS based instance. 99 | In addition, a linear [color gradient](http://en.wikipedia.org/wiki/Color_gradient) is constructed to be used later by the heatmap image generator. 100 | The `handleRESTRequest` method is implemented to intercept any REST service invocations. If the argument `operationName` has a value of `export` and the argument `outputFormat` has a value of `image`, then 101 | the call is intercepted by the `doExportImage` method otherwise the default handler is invoked. 102 | The `doExportImage` extracts from the `operationInputs` the image `size` and the current map extent `bbox` (bounding box). 103 | The latter is used to compose a MemSQL spatial query that groups and counts all taxis pickups in a "coinciding" location using the following SQL: 104 | 105 | ```sql 106 | select count(1),round(geography_latitude(pickup),3),round(geography_longitude(pickup),3) 107 | from taxistats where geography_intersects(pickup, 'POLYGON((-74.96857503 40.79939298,...))') 108 | group by 2,3 order by 1 109 | ``` 110 | 111 | Note that `pickup` is a `Point` type in the `taxistats` table onto which we can apply the `geography_latitude` and `geography_longitude` 112 | functions to extract the latitude and longitude values. To perform the spatial cookie cutting, the `geography_intersects` function is applied in the 113 | where clause with a POLYGON in [WKT](http://en.wikipedia.org/wiki/Well-known_text) format. 114 | 115 | An in memory image is created using [BufferedImage](http://docs.oracle.com/javase/7/docs/api/java/awt/image/BufferedImage.html), onto which 116 | we can draw the returned rows as colored filled rectangles. The fill color is proportionally mapped by the count value to the linear gradient 117 | color ramp, and the rectangle pixel dimensions are proportional to the map extent and image size. 118 | 119 | The buffered image is converted to a byte array in PNG format using [ImageIO](http://docs.oracle.com/javase/7/docs/api/javax/imageio/ImageIO.html), and the byte array is returned back to caller to be displayed as a layer in a web map. 120 | 121 | ![](media/webmap.png) 122 | 123 | ## A Bit of History 124 | 125 | See the above off-screen image generation using AWT? That is **exactly** the same code that I used almost 20 years ago when I implemented 126 | the first prototype of [ArcIMS](http://en.wikipedia.org/wiki/ArcIMS) using Java over a couple of [Fat Tire](http://www.newbelgium.com/beer/detail.aspx?id=7c5b394b-d7b7-486a-ac9a-316256a7b0ee) in Denver, CO. 127 | At the time, there was no [JIT](http://en.wikipedia.org/wiki/Just-in-time_compilation) in the JVM and the drawing was painfully slow. 128 | We ended up implementing the drawing and the container in C++ using an in memory graphics library that we purchased. 129 | What is old is new again! And when you get to my age, you get to see history amazingly repeat itself - hopefully for the better. 130 | -------------------------------------------------------------------------------- /media/SOI_001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mraad/ExportImageSOI/8e0e3f5f6ba699f0a2b3db36a9129a5ee63bdc27/media/SOI_001.png -------------------------------------------------------------------------------- /media/SOI_002.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mraad/ExportImageSOI/8e0e3f5f6ba699f0a2b3db36a9129a5ee63bdc27/media/SOI_002.png -------------------------------------------------------------------------------- /media/addext.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mraad/ExportImageSOI/8e0e3f5f6ba699f0a2b3db36a9129a5ee63bdc27/media/addext.png -------------------------------------------------------------------------------- /media/capabilities.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mraad/ExportImageSOI/8e0e3f5f6ba699f0a2b3db36a9129a5ee63bdc27/media/capabilities.png -------------------------------------------------------------------------------- /media/javaconfigtool.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mraad/ExportImageSOI/8e0e3f5f6ba699f0a2b3db36a9129a5ee63bdc27/media/javaconfigtool.png -------------------------------------------------------------------------------- /media/webmap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mraad/ExportImageSOI/8e0e3f5f6ba699f0a2b3db36a9129a5ee63bdc27/media/webmap.png -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | com.esri 5 | ExportImageSOI 6 | 0.32 7 | 8 | 9 | ExportImageSOI 10 | ExportImageSOI 11 | 12 | 13 | UTF-8 14 | 1.8 15 | 2.10 16 | ${scala.binary}.6 17 | 18 | 19 | 20 | 21 | cloudera-releases 22 | https://repository.cloudera.com/artifactory/cloudera-repos 23 | 24 | true 25 | 26 | 27 | false 28 | 29 | 30 | 31 | maven2-repository.dev.java.net 32 | http://download.java.net/maven/2 33 | 34 | 35 | scala-tools.org 36 | Scala-tools Maven2 Repository 37 | http://scala-tools.org/repo-releases 38 | 39 | 40 | 41 | 42 | 43 | scala-tools.org 44 | Scala-tools Maven2 Repository 45 | http://scala-tools.org/repo-releases 46 | 47 | 48 | 49 | 50 | 51 | com.esri 52 | arcobjects 53 | 10.3.1 54 | provided 55 | 56 | 57 | org.scala-lang 58 | scala-compiler 59 | ${scala.version} 60 | 61 | 62 | org.scala-lang 63 | scala-library 64 | ${scala.version} 65 | 66 | 67 | org.scala-lang 68 | scala-reflect 69 | ${scala.version} 70 | 71 | 72 | org.scala-lang 73 | scalap 74 | ${scala.version} 75 | 76 | 77 | com.esri 78 | hex-grid_${scala.binary} 79 | 1.2 80 | 81 | 82 | com.esri 83 | webmercator_${scala.binary} 84 | 1.2 85 | 86 | 87 | mysql 88 | mysql-connector-java 89 | 6.0.6 90 | 91 | 92 | 93 | 94 | 95 | 96 | net.alchim31.maven 97 | scala-maven-plugin 98 | 3.2.2 99 | 100 | 101 | 102 | 103 | 104 | net.alchim31.maven 105 | scala-maven-plugin 106 | 3.2.2 107 | 108 | 109 | scala-compile-first 110 | process-resources 111 | 112 | compile 113 | 114 | 115 | 116 | scala-test-compile-first 117 | process-test-resources 118 | 119 | testCompile 120 | 121 | 122 | 123 | 124 | ${scala.binary} 125 | ${scala.version} 126 | incremental 127 | false 128 | 129 | 130 | 131 | maven-compiler-plugin 132 | 3.6.1 133 | 134 | ${java.version} 135 | ${java.version} 136 | UTF-8 137 | 138 | 139 | 179 | 180 | org.apache.maven.plugins 181 | maven-shade-plugin 182 | 3.0.0 183 | 184 | 185 | package 186 | 187 | shade 188 | 189 | 190 | 191 | 193 | 195 | reference.conf 196 | 197 | 198 | 199 | 200 | *:* 201 | 202 | META-INF/*.SF 203 | META-INF/*.DSA 204 | META-INF/*.RSA 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | org.codehaus.gmaven 214 | groovy-maven-plugin 215 | 2.0 216 | 217 | 218 | org.safehaus.jug 219 | jug 220 | 2.0.0 221 | asl 222 | 223 | 224 | 225 | 226 | package 227 | 228 | execute 229 | 230 | 231 | 232 | import org.safehaus.uuid.UUIDGenerator 233 | import java.util.Date 234 | 235 | def uuid = UUIDGenerator.getInstance().generateRandomBasedUUID() 236 | project.properties.setProperty('soe.uuid', uuid.toString()) 237 | def date = new Date() 238 | project.properties.setProperty('soe.timestamp', date.format('EEE MMM d k:mm:ss z yyyy')) 239 | project.properties.setProperty('soe.hhmm', date.format('HHmm')) 240 | 241 | 242 | 243 | 244 | 245 | 246 | maven-assembly-plugin 247 | 2.6 248 | 249 | 250 | package 251 | 252 | single 253 | 254 | 255 | ${project.artifactId}-${project.version} 256 | 257 | false 258 | 259 | src/assembly/soe-assembly.xml 260 | 261 | 262 | 263 | 264 | 265 | 266 | maven-antrun-plugin 267 | 1.8 268 | 269 | 270 | package 271 | 272 | 273 | 275 | 276 | 277 | 278 | run 279 | 280 | 281 | 282 | 283 | 284 | 285 | -------------------------------------------------------------------------------- /src/assembly/soe-assembly.xml: -------------------------------------------------------------------------------- 1 | 5 | soe-assembly 6 | 7 | zip 8 | 9 | false 10 | 11 | 12 | ${project.build.directory} 13 | Install 14 | 15 | ${artifactId}-${version}.jar 16 | libs/*.jar 17 | 18 | 19 | 20 | 21 | 22 | src/assembly/soe-config.xml 23 | 24 | true 25 | Config.xml 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/assembly/soe-config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | ${project.name} 4 | ${project.description} 5 | ${soe.timestamp} 6 | 7 | 8 | 9 | ${project.version} 10 | 11 | 12 | 13 | {${soe.uuid}} 14 | 15 | 16 | MapServer 17 | 18 | 21 | ExportImageSOI 22 | Export Image SOI 23 | 24 | Server object to intercept export/image requests and call MemSQL AWS service 25 | 26 | 27 | false 28 | com.mysql.jdbc.Driver 29 | jdbc:mysql://10.20.40.182:3306/dmat 30 | root 31 | DMAT_Row
32 | 50000.0 33 | 500 34 | 10000 35 | 20000000 36 | 50000:loc_10,100000:loc_25,150000:loc_50,250000:loc_100,500000:loc_200,750000:loc_500,1250000:loc_1000,2500000:loc_5000,10000000:loc_10000,20000000:loc_100000 37 |
38 | 39 | 40 | 41 | false 42 | false 43 | true 44 | false 45 | 46 | 47 | 48 | 49 |
50 |
51 |
52 |
53 |
54 | -------------------------------------------------------------------------------- /src/main/java/com/esri/AbstractSOI.java: -------------------------------------------------------------------------------- 1 | package com.esri; 2 | 3 | import com.esri.arcgis.addinframework.TypeChecker; 4 | import com.esri.arcgis.interop.AutomationException; 5 | import com.esri.arcgis.server.IServerObject; 6 | import com.esri.arcgis.server.IServerObjectExtension; 7 | import com.esri.arcgis.server.IServerObjectExtensionManager; 8 | import com.esri.arcgis.server.IServerObjectExtensionManagerProxy; 9 | import com.esri.arcgis.server.IServerObjectHelper; 10 | import com.esri.arcgis.system.Cleaner; 11 | import com.esri.arcgis.system.ILog; 12 | import com.esri.arcgis.system.IPropertySet; 13 | import com.esri.arcgis.system.IRESTRequestHandler; 14 | import com.esri.arcgis.system.IRESTRequestHandlerProxy; 15 | import com.esri.arcgis.system.IRequestHandler; 16 | import com.esri.arcgis.system.IRequestHandler2; 17 | import com.esri.arcgis.system.IRequestHandler2Proxy; 18 | import com.esri.arcgis.system.IRequestHandlerProxy; 19 | import com.esri.arcgis.system.IServerEnvironment; 20 | import com.esri.arcgis.system.IWebRequestHandler; 21 | import com.esri.arcgis.system.IWebRequestHandlerProxy; 22 | import com.esri.arcgis.system.ServerUtilities; 23 | 24 | import java.io.IOException; 25 | import java.util.HashMap; 26 | import java.util.Map; 27 | 28 | /** 29 | * For an SOE to act as in interceptor, it needs to implement all request handler interfaces: 30 | * IRESTRequestHandler, IWebRequestHandler, IRequestHandler2, IRequestHandler 31 | * now the SOE/SOI can intercept all types of calls to ArcObjects or custom SOEs. 32 | */ 33 | public abstract class AbstractSOI implements 34 | IServerObjectExtension, 35 | IRESTRequestHandler, 36 | IWebRequestHandler, 37 | IRequestHandler2 38 | { 39 | protected ILog log; 40 | protected IServerObjectHelper serverObjectHelper; 41 | protected IServerObject serverObject; 42 | protected IServerEnvironment serverEnvironment; 43 | protected IRESTRequestHandler restRequestHandler; 44 | protected Map extensionCache = new HashMap<>(); //cache to store SOE references 45 | 46 | public void init(IServerObjectHelper soh) throws IOException, AutomationException 47 | { 48 | this.log = ServerUtilities.getServerLogger(); 49 | this.serverEnvironment = ServerUtilities2.getServerEnvironment(); 50 | this.serverObjectHelper = soh; 51 | this.serverObject = soh.getServerObject(); 52 | this.restRequestHandler = new IRESTRequestHandlerProxy(this.serverObject); 53 | } 54 | 55 | @Override 56 | public String getSchema() throws IOException, AutomationException 57 | { 58 | final IRESTRequestHandler handler = findRestRequestHandlerDelegate(); 59 | return handler == null ? null : handler.getSchema(); 60 | } 61 | 62 | @Override 63 | public String handleStringRequest(String capabilities, String request) 64 | throws IOException, AutomationException 65 | { 66 | final IRequestHandler handler = findRequestHandlerDelegate(); 67 | return handler == null ? null : handler.handleStringRequest(capabilities, request); 68 | } 69 | 70 | @Override 71 | public byte[] handleStringWebRequest(int httpMethod, 72 | String requestURL, 73 | String queryString, 74 | String capabilities, 75 | String requestData, 76 | String[] responseContentType, 77 | int[] respDataType) 78 | throws IOException, AutomationException 79 | { 80 | final IWebRequestHandler handler = findWebRequestHandlerDelegate(); 81 | return handler == null ? null : handler.handleStringWebRequest(httpMethod, 82 | requestURL, queryString, capabilities, requestData, 83 | responseContentType, respDataType); 84 | } 85 | 86 | @Override 87 | public byte[] handleBinaryRequest2(String capabilities, byte[] request) 88 | throws IOException, AutomationException 89 | { 90 | final IRequestHandler2 handler = findRequestHandler2Delegate(); 91 | return handler == null ? null : handler.handleBinaryRequest2(capabilities, request); 92 | } 93 | 94 | @Override 95 | public byte[] handleBinaryRequest(byte[] request) 96 | throws IOException, AutomationException 97 | { 98 | final IRequestHandler handler = findRequestHandlerDelegate(); 99 | return handler == null ? null : handler.handleBinaryRequest(request); 100 | } 101 | 102 | protected abstract void preShutdown(); 103 | 104 | public void shutdown() throws IOException, AutomationException 105 | { 106 | preShutdown(); 107 | Cleaner.release(restRequestHandler); 108 | Cleaner.release(serverObject); 109 | Cleaner.release(serverObjectHelper); 110 | Cleaner.release(serverEnvironment); 111 | Cleaner.release(log); 112 | } 113 | 114 | /** 115 | * Utility functions that return the appropriate delegate interface based on input request. 116 | * 117 | * @return the IRESTRequestHandler 118 | */ 119 | protected IRESTRequestHandler findRestRequestHandlerDelegate() throws IOException 120 | { 121 | final IPropertySet props = serverEnvironment.getProperties(); 122 | // Check if there is an extension name set 123 | String extensionName; 124 | try 125 | { 126 | extensionName = (String) props.getProperty("ExtensionName"); 127 | } 128 | catch (Exception e) 129 | { 130 | extensionName = null; 131 | } 132 | if (extensionName == null || extensionName.isEmpty()) 133 | { 134 | return restRequestHandler; 135 | } 136 | else 137 | { 138 | // Get the extension reference from cache if available 139 | if (extensionCache.containsKey(extensionName)) 140 | { 141 | return new IRESTRequestHandlerProxy(extensionCache.get(extensionName)); 142 | } 143 | // This request is to be made on a specific extension 144 | // so we find the extension from the extension manager 145 | final IServerObjectExtensionManager extnMgr = new IServerObjectExtensionManagerProxy(serverObject); 146 | final IServerObjectExtension soe = extnMgr.findExtensionByTypeName(extensionName); 147 | if (TypeChecker.instanceOf(soe, IRESTRequestHandler.class)) 148 | { 149 | extensionCache.put(extensionName, soe); 150 | return new IRESTRequestHandlerProxy(soe); 151 | } 152 | else 153 | { 154 | return null; 155 | } 156 | } 157 | } 158 | 159 | /** 160 | * Utility functions that return the appropriate delegate interface based on input request. 161 | * 162 | * @return the IRequestHandler 163 | */ 164 | @SuppressWarnings("deprecation") 165 | protected IRequestHandler findRequestHandlerDelegate() throws IOException 166 | { 167 | // Get the server environment 168 | // IServerEnvironment2 env = getServerEnvironment(); 169 | // Get the environment properties 170 | IPropertySet props = serverEnvironment.getProperties(); 171 | // Check if there is an extension name set 172 | String extensionName; 173 | try 174 | { 175 | extensionName = (String) props.getProperty("ExtensionName"); 176 | } 177 | catch (Exception e) 178 | { 179 | extensionName = null; 180 | } 181 | if (extensionName == null || extensionName.isEmpty()) 182 | { 183 | // No extension has been set - return reference to parent parent server object 184 | if (TypeChecker.instanceOf(serverObject, IRequestHandler.class)) 185 | { 186 | return new IRequestHandlerProxy(serverObject); 187 | } 188 | else 189 | { 190 | return null; 191 | } 192 | } 193 | else 194 | { 195 | // Get the extension reference from cache if available 196 | if (extensionCache.containsKey(extensionName)) 197 | { 198 | return new IRequestHandlerProxy(extensionCache.get(extensionName)); 199 | } 200 | // This request is to be made on a specific extension 201 | // so we find the extension from the extension manager 202 | IServerObjectExtensionManager extnMgr = new IServerObjectExtensionManagerProxy(serverObject); 203 | IServerObjectExtension soe = extnMgr.findExtensionByTypeName(extensionName); 204 | if (TypeChecker.instanceOf(soe, IRequestHandler.class)) 205 | { 206 | extensionCache.put(extensionName, soe); 207 | return new IRequestHandlerProxy(soe); 208 | } 209 | else 210 | { 211 | return null; 212 | } 213 | } 214 | } 215 | 216 | /** 217 | * Utility functions that return the appropriate delegate interface based on input request. 218 | * 219 | * @return the IRequestHandler2 220 | */ 221 | @SuppressWarnings("deprecation") 222 | protected IRequestHandler2 findRequestHandler2Delegate() throws IOException 223 | { 224 | // Get the server environment 225 | // IServerEnvironment2 env = getServerEnvironment(); 226 | // Get the environment properties 227 | IPropertySet props = serverEnvironment.getProperties(); 228 | // Check if there is an extension name set 229 | String extensionName; 230 | try 231 | { 232 | extensionName = (String) props.getProperty("ExtensionName"); 233 | } 234 | catch (Exception e) 235 | { 236 | extensionName = null; 237 | } 238 | if (extensionName == null || extensionName.isEmpty()) 239 | { 240 | // No extension has been set - return reference to parent parent server object 241 | if (TypeChecker.instanceOf(serverObject, IRequestHandler2.class)) 242 | { 243 | return new IRequestHandler2Proxy(serverObject); 244 | } 245 | else 246 | { 247 | return null; 248 | } 249 | } 250 | else 251 | { 252 | // Get the extension reference from cache if available 253 | if (extensionCache.containsKey(extensionName)) 254 | { 255 | return new IRequestHandler2Proxy(extensionCache.get(extensionName)); 256 | } 257 | // This request is to be made on a specific extension 258 | // so we find the extension from the extension manager 259 | IServerObjectExtensionManager extnMgr = new IServerObjectExtensionManagerProxy(serverObject); 260 | IServerObjectExtension soe = extnMgr.findExtensionByTypeName(extensionName); 261 | if (TypeChecker.instanceOf(soe, IRequestHandler2.class)) 262 | { 263 | extensionCache.put(extensionName, soe); 264 | return new IRequestHandler2Proxy(soe); 265 | } 266 | else 267 | { 268 | return null; 269 | } 270 | } 271 | } 272 | 273 | /** 274 | * Utility functions that return the appropriate delegate interface based on input request. 275 | * 276 | * @return the IWebRequestHandler 277 | */ 278 | @SuppressWarnings("deprecation") 279 | protected IWebRequestHandler findWebRequestHandlerDelegate() throws IOException 280 | { 281 | // Get the server environment 282 | // Get the environment properties 283 | IPropertySet props = serverEnvironment.getProperties(); 284 | // Check if there is an extension name set 285 | String extensionName; 286 | try 287 | { 288 | extensionName = (String) props.getProperty("ExtensionName"); 289 | } 290 | catch (Exception e) 291 | { 292 | extensionName = null; 293 | } 294 | if (extensionName == null || extensionName.isEmpty()) 295 | { 296 | // No extension has been set - return reference to parent parent server object 297 | if (TypeChecker.instanceOf(serverObject, IWebRequestHandler.class)) 298 | { 299 | return new IWebRequestHandlerProxy(serverObject); 300 | } 301 | else 302 | { 303 | return null; 304 | } 305 | } 306 | else 307 | { 308 | // Get the extension reference from cache if available 309 | if (extensionCache.containsKey(extensionName)) 310 | { 311 | return new IWebRequestHandlerProxy(extensionCache.get(extensionName)); 312 | } 313 | // This request is to be made on a specific extension 314 | // so we find the extension from the extension manager 315 | IServerObjectExtensionManager extnMgr = new IServerObjectExtensionManagerProxy(serverObject); 316 | IServerObjectExtension soe = extnMgr.findExtensionByTypeName(extensionName); 317 | if (TypeChecker.instanceOf(soe, IWebRequestHandler.class)) 318 | { 319 | extensionCache.put(extensionName, soe); 320 | return new IWebRequestHandlerProxy(soe); 321 | } 322 | else 323 | { 324 | return null; 325 | } 326 | } 327 | } 328 | } 329 | -------------------------------------------------------------------------------- /src/main/java/com/esri/ColorMapper.java: -------------------------------------------------------------------------------- 1 | package com.esri; 2 | 3 | import java.awt.Color; 4 | import java.awt.Graphics2D; 5 | import java.awt.LinearGradientPaint; 6 | import java.awt.MultipleGradientPaint; 7 | import java.awt.image.BufferedImage; 8 | import java.io.IOException; 9 | import java.io.Serializable; 10 | 11 | /** 12 | */ 13 | public class ColorMapper implements Serializable 14 | { 15 | public Color[] colors; 16 | 17 | public void construct() throws IOException 18 | { 19 | final BufferedImage bi = createGradientImage( 20 | new Color(0xFF, 0xFF, 0x00, 128), 21 | new Color(0xFF, 0x7F, 0x00, 128), 22 | new Color(0xFF, 0x00, 0x00, 128) 23 | ); 24 | colors = new Color[256]; 25 | for (int i = 0; i < 256; i++) 26 | { 27 | colors[i] = new Color(bi.getRGB(i, 0), true); 28 | } 29 | } 30 | 31 | private BufferedImage createGradientImage(Color... colors) 32 | { 33 | final float[] fractions = new float[colors.length]; 34 | 35 | final float step = 1.0F / colors.length; 36 | 37 | for (int i = 0; i < colors.length; i++) 38 | { 39 | fractions[i] = i * step; 40 | } 41 | 42 | final LinearGradientPaint gradient = new LinearGradientPaint(0, 0, 256, 1, fractions, colors, MultipleGradientPaint.CycleMethod.REPEAT); 43 | final BufferedImage bi = new BufferedImage(256, 1, BufferedImage.TYPE_INT_ARGB); 44 | final Graphics2D g = bi.createGraphics(); 45 | try 46 | { 47 | g.setPaint(gradient); 48 | g.fillRect(0, 0, 256, 1); 49 | } 50 | finally 51 | { 52 | g.dispose(); 53 | } 54 | return bi; 55 | } 56 | 57 | public Color getColor(final int index) 58 | { 59 | return colors[index]; 60 | } 61 | 62 | /* 63 | public Color getColor(final float val, final float min, final float del) 64 | { 65 | return getColor((int) (255 * (val - min) / del)); 66 | } 67 | */ 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/com/esri/HexCell.java: -------------------------------------------------------------------------------- 1 | package com.esri; 2 | 3 | import java.io.Serializable; 4 | 5 | public class HexCell implements Serializable 6 | { 7 | final double[] x = new double[7]; 8 | final double[] y = new double[7]; 9 | 10 | public HexCell(double size) 11 | { 12 | for (int i = 0; i < 7; i++) 13 | { 14 | final double angle = Math.PI * ((i % 6) + 0.5) / 3.0; 15 | x[i] = size * Math.cos(angle); 16 | y[i] = size * Math.sin(angle); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/esri/ServerUtilities2.java: -------------------------------------------------------------------------------- 1 | package com.esri; 2 | 3 | import com.esri.arcgis.system.EnvironmentManager; 4 | import com.esri.arcgis.system.IServerEnvironment2; 5 | import com.esri.arcgis.system.IServerEnvironment2Proxy; 6 | import com.esri.arcgis.system.UID; 7 | 8 | import java.io.IOException; 9 | 10 | /** 11 | */ 12 | public class ServerUtilities2 13 | { 14 | public final static IServerEnvironment2 getServerEnvironment() throws IOException 15 | { 16 | final EnvironmentManager envMgr = new EnvironmentManager(); 17 | final UID envUID = new UID(); 18 | envUID.setValue("{32d4c328-e473-4615-922c-63c108f55e60}"); 19 | return new IServerEnvironment2Proxy(envMgr.getEnvironment(envUID)); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/resources/log4j.properties: -------------------------------------------------------------------------------- 1 | # Set everything to be logged to the console 2 | log4j.rootCategory=INFO, console 3 | log4j.appender.console=org.apache.log4j.ConsoleAppender 4 | log4j.appender.console.target=System.err 5 | log4j.appender.console.layout=org.apache.log4j.PatternLayout 6 | log4j.appender.console.layout.ConversionPattern=%d{HH:mm:ss} %p %c{1}: %m%n 7 | 8 | # Settings to quiet third party logs that are too verbose 9 | log4j.logger.org.eclipse.jetty=WARN 10 | log4j.logger.org.eclipse.jetty.util.component.AbstractLifeCycle=ERROR 11 | log4j.logger.org.apache.spark.repl.SparkIMain$exprTyper=INFO 12 | log4j.logger.org.apache.spark.repl.SparkILoop$SparkILoopInterpreter=INFO 13 | -------------------------------------------------------------------------------- /src/main/scala/com/esri/ExportImageSOI.scala: -------------------------------------------------------------------------------- 1 | package com.esri 2 | 3 | import java.awt.image.BufferedImage 4 | import java.awt.{Color, RenderingHints} 5 | import java.io.ByteArrayOutputStream 6 | import java.sql.{Connection, DriverManager} 7 | import javax.imageio.ImageIO 8 | 9 | import com.esri.arcgis.interop.extn.ArcGISExtension 10 | import com.esri.arcgis.server.json.JSONObject 11 | import com.esri.arcgis.system.{IObjectConstruct, IPropertySet, IRESTRequestHandler} 12 | import com.esri.webmercator._ 13 | 14 | import scala.collection.JavaConversions._ 15 | 16 | @ArcGISExtension 17 | class ExportImageSOI extends AbstractSOI with IObjectConstruct { 18 | 19 | val colorMapper = new ColorMapper() 20 | 21 | var showRect: Boolean = _ 22 | var connection: Connection = _ 23 | var tableName: String = _ 24 | var imagePNG: String = _ 25 | var minCount: Double = _ 26 | var maxCount: Double = _ 27 | var delCount: Double = _ 28 | var scaleMax: Double = _ 29 | var scaleLocArr: Array[ScaleLoc] = _ 30 | val dpm = 96.0 /*DPI*/ * 39.3700787 // dots per meter 31 | 32 | override def construct(propertySet: IPropertySet): Unit = { 33 | // log.addMessage(3, 200, "ExportImageSOI::construct") 34 | try { 35 | colorMapper.construct() 36 | 37 | showRect = propertySet.getProperty("showRect").asInstanceOf[String].toBoolean 38 | minCount = propertySet.getProperty("minCount").asInstanceOf[String].toDouble 39 | maxCount = propertySet.getProperty("maxCount").asInstanceOf[String].toDouble 40 | delCount = maxCount - minCount 41 | 42 | scaleMax = propertySet.getProperty("maxScale").asInstanceOf[String].toDouble 43 | scaleLocArr = propertySet.getProperty("scales").asInstanceOf[String].split(',').map(ScaleLoc(_)) 44 | 45 | tableName = propertySet.getProperty("table").asInstanceOf[String] 46 | 47 | val json = new JSONObject(Map("Content-Type" -> "image/png")) 48 | imagePNG = json.toString() 49 | 50 | Class.forName(propertySet.getProperty("driver").asInstanceOf[String]) 51 | 52 | connection = DriverManager.getConnection( 53 | propertySet.getProperty("connection").asInstanceOf[String], 54 | propertySet.getProperty("username").asInstanceOf[String], 55 | "" // No password ??? 56 | ) 57 | 58 | // log.addMessage(3, 200, "Constructed.") 59 | } 60 | catch { 61 | case t: Throwable => log.addMessage(3, 200, t.toString()) 62 | } 63 | } 64 | 65 | val sizeRE = "^(\\d+),(\\d+)$".r 66 | 67 | def doExportImage(operationInput: String, responseProperties: Array[String]) = { 68 | val jsonInput = new JSONObject(operationInput) 69 | val (imgW, imgH) = jsonInput.getString("size") match { 70 | case sizeRE(wt, ht) => (wt.toInt, ht.toInt) 71 | case _ => (400, 400) 72 | } 73 | val (xmin, ymin, xmax, ymax) = jsonInput.getString("bbox") match { 74 | case text: String => { 75 | val tokens = text.split(',') 76 | (tokens(0).toDouble, tokens(1).toDouble, tokens(2).toDouble, tokens(3).toDouble) 77 | } 78 | case _ => (1.0, 1.0, -1.0, -1.0) 79 | } 80 | val defExp = if (jsonInput.has("definitionExpression")) 81 | jsonInput.get("definitionExpression") match { 82 | case text: String => " AND " + text 83 | case _ => "" 84 | } 85 | else "" 86 | 87 | val bi = new BufferedImage(imgW, imgH, BufferedImage.TYPE_INT_ARGB) 88 | val g = bi.createGraphics() 89 | try { 90 | g.setRenderingHints(Map( 91 | RenderingHints.KEY_ANTIALIASING -> RenderingHints.VALUE_ANTIALIAS_ON, 92 | RenderingHints.KEY_RENDERING -> RenderingHints.VALUE_RENDER_QUALITY 93 | )) 94 | g.setBackground(Color.WHITE) 95 | val scale = (xmax - xmin) * dpm / imgW 96 | if (scale > 0 && scale < scaleMax) { 97 | scaleLocArr.find(_.find(scale)) match { 98 | case Some(scaleLoc) => 99 | try { 100 | val hexGrid = scaleLoc.toHexGrid 101 | val minLon = xmin toLongitude 102 | val maxLon = xmax toLongitude 103 | val minLat = ymin toLatitude 104 | val maxLat = ymax toLatitude 105 | val sb = new StringBuilder("POLYGON((") 106 | sb.append(minLon).append(' ').append(minLat).append(',') 107 | .append(maxLon).append(' ').append(minLat).append(',') 108 | .append(maxLon).append(' ').append(maxLat).append(',') 109 | .append(minLon).append(' ').append(maxLat).append(',') 110 | .append(minLon).append(' ').append(minLat).append("))") 111 | val preparedStatement = connection.prepareStatement( 112 | s"""select ${scaleLoc.loc},count(1) from $tableName where geography_intersects(shape, ?) $defExp group by 1""" 113 | ) 114 | try { 115 | preparedStatement.setString(1, sb.toString) 116 | val resultSet = preparedStatement.executeQuery 117 | try { 118 | val dx = xmax - xmin 119 | val dy = ymax - ymin 120 | val px = new Array[Int](7) 121 | val py = new Array[Int](7) 122 | val hexCell = new HexCell(hexGrid.size) 123 | while (resultSet.next) { 124 | val loc = resultSet.getString(1) 125 | val pop = resultSet.getInt(2) 126 | val arr = loc.split(':') 127 | val row = arr(0).toLong 128 | val col = arr(1).toLong 129 | val cellXY = hexGrid.convertRowColToHexXY(row, col) 130 | 131 | var i = 0 132 | while (i < 7) { 133 | val hx = cellXY.x + hexCell.x(i) 134 | val hy = cellXY.y + hexCell.y(i) 135 | val fx = (hx - xmin) / dx 136 | val fy = 1.0 - (hy - ymin) / dy 137 | px(i) = (imgW * fx).toInt 138 | py(i) = (imgH * fy).toInt 139 | i += 1 140 | } 141 | 142 | val colorIndex = if (pop < minCount) 0 143 | else if (pop > maxCount) 255 144 | else math.floor(255 * (pop - minCount) / delCount).toInt 145 | g.setColor(colorMapper.getColor(colorIndex)) 146 | g.fillPolygon(px, py, 7) 147 | g.setColor(Color.GRAY) 148 | g.drawPolygon(px, py, 7) 149 | } 150 | } finally { 151 | resultSet.close() 152 | } 153 | } finally { 154 | preparedStatement.close() 155 | } 156 | g.setColor(Color.GREEN) 157 | } 158 | catch { 159 | case t: Throwable => { 160 | t.getStackTrace.foreach(ste => { 161 | log.addMessage(3, 200, ste.toString) 162 | }) 163 | g.setColor(Color.RED) 164 | } 165 | } 166 | case _ => { 167 | g.setColor(Color.BLUE) 168 | } 169 | } 170 | } 171 | else { 172 | g.setColor(Color.BLUE) 173 | } 174 | if (showRect) 175 | g.drawRect(0, 0, imgW - 1, imgH - 1) 176 | else 177 | g.drawRect(0, 0, 1, 1) 178 | } 179 | finally { 180 | g.dispose() 181 | } 182 | 183 | responseProperties(0) = imagePNG 184 | 185 | val baos = new ByteArrayOutputStream(bi.getWidth * bi.getHeight) 186 | ImageIO.write(bi, "PNG", baos) 187 | baos.toByteArray 188 | } 189 | 190 | override def handleRESTRequest(capabilities: String, 191 | resourceName: String, 192 | operationName: String, 193 | operationInput: String, 194 | outputFormat: String, 195 | requestProperties: String, 196 | responseProperties: Array[String] 197 | ) = { 198 | 199 | log.addMessage(3, 200, s"r=$resourceName o=$operationName i=$operationInput f=$outputFormat") 200 | 201 | (operationName, outputFormat) match { 202 | case ("export", "image") => doExportImage(operationInput, responseProperties) 203 | case _ => 204 | findRestRequestHandlerDelegate() match { 205 | case inst: IRESTRequestHandler => inst.handleRESTRequest( 206 | capabilities, resourceName, operationName, operationInput, outputFormat, requestProperties, responseProperties 207 | ) 208 | case _ => null 209 | } 210 | } 211 | } 212 | 213 | override protected def preShutdown(): Unit = { 214 | // log.addMessage(3, 200, "ExportImageSOI::preShutdown") 215 | try { 216 | connection.close() 217 | } 218 | catch { 219 | case t: Throwable => log.addMessage(3, 200, t.toString) 220 | } 221 | } 222 | } 223 | -------------------------------------------------------------------------------- /src/main/scala/com/esri/ScaleLoc.scala: -------------------------------------------------------------------------------- 1 | package com.esri 2 | 3 | import com.esri.hex.HexGrid 4 | 5 | class ScaleLoc(scale: Double, val loc: String) extends Serializable { 6 | def find(mapScale: Double) = { 7 | mapScale <= scale 8 | } 9 | 10 | def toHexGrid() = { 11 | loc.split('_') match { 12 | case Array(_, size) => { 13 | HexGrid(size.toDouble, -20000000.0, -20000000.0) 14 | } 15 | case _ => HexGrid(1000000.0, -20000000.0, -20000000.0) 16 | } 17 | } 18 | } 19 | 20 | object ScaleLoc extends Serializable { 21 | def apply(text: String) = { 22 | text.split(':') match { 23 | case Array(scale, loc) => new ScaleLoc(scale.toDouble, loc) 24 | case _ => new ScaleLoc(Double.PositiveInfinity, text) 25 | } 26 | } 27 | } 28 | --------------------------------------------------------------------------------