├── .github ├── dependabot.yml └── workflows │ └── ci.yml ├── .gitignore ├── LICENSE ├── README.md ├── RELEASE_NOTES.md ├── generate-site.sh ├── grumpy-app ├── pom.xml └── src │ └── main │ ├── java │ └── com │ │ └── github │ │ └── davidmoten │ │ └── grumpy │ │ └── wms │ │ └── app │ │ └── WmsServlet.java │ └── webapp │ ├── WEB-INF │ ├── classes │ │ └── log4j.properties │ └── web.xml │ ├── css │ ├── style.css │ └── theme-style.css │ ├── index.html │ ├── js │ ├── base-layers-3857.js │ ├── base-layers-4326.js │ └── layers.js │ ├── map-3857.jsp │ └── map-4326.jsp ├── grumpy-core ├── README.md ├── pom.xml └── src │ ├── main │ └── java │ │ └── com │ │ └── github │ │ └── davidmoten │ │ └── grumpy │ │ └── core │ │ └── Position.java │ └── test │ ├── java │ └── com │ │ └── github │ │ └── davidmoten │ │ └── grumpy │ │ └── core │ │ └── PositionTest.java │ └── resources │ └── log4j.properties ├── grumpy-ogc-layers ├── pom.xml └── src │ └── main │ ├── java │ └── com │ │ └── github │ │ └── davidmoten │ │ └── grumpy │ │ └── wms │ │ └── layer │ │ └── darkness │ │ ├── DarknessLayer.java │ │ ├── SunUtil.java │ │ └── TimeUtil.java │ └── resources │ └── sunny.png ├── grumpy-ogc ├── pom.xml └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── github │ │ │ └── davidmoten │ │ │ ├── grumpy │ │ │ ├── function │ │ │ │ └── Function.java │ │ │ └── wms │ │ │ │ ├── Capabilities.java │ │ │ │ ├── CapabilitiesLayer.java │ │ │ │ ├── CapabilitiesProvider.java │ │ │ │ ├── CapabilitiesProviderEmpty.java │ │ │ │ ├── CapabilitiesProviderFromCapabilities.java │ │ │ │ ├── CapabilitiesProviderFromClasspath.java │ │ │ │ ├── HasLayerFeatures.java │ │ │ │ ├── ImageCache.java │ │ │ │ ├── ImageWriter.java │ │ │ │ ├── ImageWriterDefault.java │ │ │ │ ├── Layer.java │ │ │ │ ├── LayerFeatures.java │ │ │ │ ├── LayerManager.java │ │ │ │ ├── Layers.java │ │ │ │ ├── LayersBuilder.java │ │ │ │ ├── MissingMandatoryParameterException.java │ │ │ │ ├── RendererUtil.java │ │ │ │ ├── UnknownParameterException.java │ │ │ │ ├── WmsRequest.java │ │ │ │ ├── WmsRequestProcessor.java │ │ │ │ ├── WmsServletRequestProcessor.java │ │ │ │ ├── WmsUtil.java │ │ │ │ └── reduction │ │ │ │ ├── RectangleSampler.java │ │ │ │ ├── RectangleSamplerCorners.java │ │ │ │ ├── RectangleSamplerGrid.java │ │ │ │ ├── RectangleUtil.java │ │ │ │ ├── Reducer.java │ │ │ │ └── ValueRenderer.java │ │ │ └── util │ │ │ └── servlet │ │ │ └── RequestUtil.java │ └── resources │ │ ├── wms-capabilities-empty.xml │ │ └── wms-capabilities-template.xml │ └── test │ ├── java │ └── com │ │ └── github │ │ └── davidmoten │ │ └── grumpy │ │ └── wms │ │ ├── CapabilitiesProviderFromCapabilitiesTest.java │ │ ├── RendererUtilTest.java │ │ └── reduction │ │ └── BoundsSamplerMaxSizeTest.java │ └── resources │ └── log4j.properties ├── grumpy-projection ├── pom.xml └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── github │ │ │ └── davidmoten │ │ │ └── grumpy │ │ │ ├── projection │ │ │ ├── FeatureUtil.java │ │ │ ├── Projector.java │ │ │ ├── ProjectorBounds.java │ │ │ └── ProjectorTarget.java │ │ │ └── util │ │ │ └── NearBSpline.java │ └── resources │ │ └── epsg │ │ ├── EPSG_102100.txt │ │ └── EPSG_900913.txt │ └── test │ └── java │ └── com │ └── github │ └── davidmoten │ └── grumpy │ └── ProjectorTest.java ├── pom.xml ├── src └── docs │ ├── craft.png │ ├── demo.png │ ├── demo2.png │ ├── demo3.png │ └── demo4.png └── wms-demo ├── pom.xml └── src └── main ├── java └── com │ └── github │ └── davidmoten │ └── grumpy │ └── wms │ └── demo │ ├── CustomLayer.java │ ├── FiddleLayer.java │ └── WmsServlet.java ├── resources └── wms-capabilities.xml └── webapp ├── WEB-INF ├── classes │ └── log4j.properties └── web.xml ├── css ├── style.css └── theme-style.css ├── index.html ├── js ├── base-layers-3857.js ├── base-layers-4326.js └── layers.js ├── map-3857.jsp └── map-4326.jsp /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "maven" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "daily" 12 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | uses: davidmoten/workflows/.github/workflows/ci.yml@master 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | pom.xml.tag 3 | pom.xml.releaseBackup 4 | pom.xml.next 5 | release.properties 6 | .classpath 7 | .project 8 | .settings 9 | -------------------------------------------------------------------------------- /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 {yyyy} {name of copyright owner} 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. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | grumpy 2 | ============= 3 |
4 | [![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.github.davidmoten/grumpy/badge.svg?style=flat)](https://maven-badges.herokuapp.com/maven-central/com.github.davidmoten/grumpy)
5 | [![codecov](https://codecov.io/gh/davidmoten/grumpy/branch/master/graph/badge.svg)](https://codecov.io/gh/davidmoten/grumpy)
6 | 7 | [OGC WMS](http://www.opengeospatial.org/standards/wms) 1.3.0 Server in java allowing custom rendering of WMS layers using projection utilities and Graphics2D. 8 | 9 | Status: *released to Maven Central* 10 | 11 | [Release Notes](RELEASE_NOTES.md) 12 | 13 | 14 | 15 | [Maven reports](http://davidmoten.github.io/grumpy/project-reports.html) including [javadocs](http://davidmoten.github.io/grumpy/apidocs/index.html). 16 | 17 | Features 18 | ---------- 19 | * Great circle navigation utilities in ```grumpy-core``` 20 | * Map projection utilities in ```grumpy-projection``` 21 | * WMS Server utilities in ```grumpy-ogc``` 22 | * Create a lightweight WMS server (about 12MB war) 23 | * Supports BBOX parameters handled differently by WMS 1.1.1 and 1.3 24 | * Handles boundary discontinuities with ```Reducer``` and ```RendererUtil``` methods 25 | * War artifact to deploy Darkness layer as WMS service (```grumpy-app```) 26 | 27 | Serverless AWS implementation of grumpy with OpenLayers 6 and ArcGIS base layers: 28 | 29 | 30 | 31 | Run demo locally 32 | ------------------ 33 | ``` 34 | git clone https://github.com/davidmoten/grumpy.git 35 | cd grumpy 36 | mvn clean install 37 | cd wms-demo 38 | mvn jetty:run 39 | ``` 40 | 41 | Go to [http://localhost:8080](http://localhost:8080/wms-demo) with a browser. 42 | 43 | And at the map link you will see this: 44 | 45 | 46 | 47 | This demonstrates a custom filled shape and some text that is placed with transparency over the position of Canberra on the map. Notice that the borders are great circle paths. 48 | 49 | Click in the Canberra box and you will see a demo of WMS GetFeatureInfo: 50 | 51 | 52 | 53 | How is it all done? Easy! 54 | 55 | Getting started 56 | ------------------- 57 | To make your own WMS service add this dependency to the pom.xml of your war project: 58 | ```xml 59 | 60 | com.github.davidmoten 61 | grumpy-ogc 62 | VERSION_HERE 63 | 64 | ``` 65 | 66 | How to make your own WMS 67 | --------------------------- 68 | Using a war project (you could just copy the ```wms-demo``` project and change its artifact and group id, remove the parent reference as well): 69 | 70 | ### Create a layer: 71 | 72 | See [CustomLayer.java](wms-demo/src/main/java/com/github/davidmoten/grumpy/wms/demo/CustomLayer.java) for how to render a layer using a ```Projector``` and a ```RendererUtil```. 73 | 74 | ### Create a servlet to serve the layer and capabilities: 75 | 76 | See [WmsServlet.java](wms-demo%2Fsrc%2Fmain%2Fjava%2Fcom%2Fgithub%2Fdavidmoten%2Fgrumpy%2Fwms%2Fdemo%2FWmsServlet.java) for how an ```HttpServlet``` is created to server WMS requests. You do need to register this servlet against a url of course in [web.xml](wms-demo/src/main/webapp/WEB-INF/web.xml). 77 | 78 | ### Define the service capabilities: 79 | 80 | Note that this enables service discovery and is required from WMS clients like ArcGIS but is not required for an OpenLayers WMS client like in ```wms-demo```. 81 | 82 | There are two options 83 | 84 | * Use ```Capabilities.builder()``` as in [WmsServlet.java](wms-demo%2Fsrc%2Fmain%2Fjava%2Fcom%2Fgithub%2Fdavidmoten%2Fgrumpy%2Fwms%2Fdemo%2FWmsServlet.java) 85 | * Use a classpath resource such as ```/wms-capabilities.xml``` with ```WmsServletRequestProcessor.builder().capabilitiesFromClasspath()``` 86 | 87 | See [wms-capabilities.xml](wms-demo%2Fsrc%2Fmain%2Fresources%2Fwms-capabilities.xml) which should conform to the OGC WMS 1.3 schema. 88 | 89 | From this point you have a working WMS service against the url for the ```WmsServlet```! 90 | 91 | ### View the WMS: 92 | Example WMS clients are included in ```wms-demo``` in [map-3857.jsp](wms-demo%2Fsrc%2Fmain%2Fwebapp%2Fmap-3857.jsp) and [map-4326.jsp](wms-demo%2Fsrc%2Fmain%2Fwebapp%2Fmap-4326.jsp). The custom layer is referenced in [layers.js](wms-demo/src/main/webapp/js/layers.js). 93 | 94 | ```wms-demo``` project uses [OpenLayers](http://openlayers.org/) javascript libraries and google maps v3 to display the world and the custom layer using the Spherical Mercator projection (EPSG 3857). See OpenLayers [documentation](http://docs.openlayers.org/) and [examples](http://openlayers.org/dev/examples/) to play with this client as you see fit. 95 | 96 | How to build 97 | ---------------- 98 | ``` 99 | git clone https://github.com/davidmoten/grumpy.git 100 | cd grumpy 101 | mvn clean install 102 | ``` 103 | 104 | Drawing Regions 105 | ---------------- 106 | Drawing regions that extend over the polar areas is a bit tricky. The safest method is to use ```Reducer.render``` with an *is inside* value function and a filling ```ValueRenderer```. See [FiddleLayer.java](wms-demo%2Fsrc%2Fmain%2Fjava%2Fcom%2Fgithub%2Fdavidmoten%2Fgrumpy%2Fwms%2Fdemo%2FFiddleLayer.java). The Fiddle layer is visible in the demo. 107 | 108 | Another example using the ```Reducer``` is the [Darkness layer](grumpy-ogc-layers/src/main/java/com/github/davidmoten/grumpy/wms/layer/darkness/DarknessLayer.java) (also in the demo). 109 | 110 | What is the Reducer? 111 | --------------------- 112 | The reducer is an abstraction of Steven Ring's original idea for implementing the [Darkness layer](grumpy-ogc-layers/src/main/java/com/github/davidmoten/grumpy/wms/layer/darkness/DarknessLayer.java). A reduction rendering comprises: 113 | 114 | * a value function 115 | * a rectangle sampler 116 | * a rectangle renderer 117 | 118 | In short the recursive algorithm is to start with a rectangle the size of the screen and if the points sampled across the rectangle (according to the *rectangle sampler*) differ in value (calculated using the *value function*) then the rectangle is split into sub-rectangles and the process continued recursively. Once the values sampled across a rectangle are all the same the rectangle is rendered with its one value using the *rectangle renderer*. 119 | 120 | It's a useful way of handling projection weirdness by concentrating on the screen pixels. If the region you are trying to draw is irregular or small then you might need to use a custom sampler to be sure the region is detected at low scales. The ```Darkness``` layer is an example of a region that is smooth and large enough that corner sampling of rectangles is sufficient. 121 | 122 | The Darkness layer 123 | ---------------------- 124 | The module ```grumpy-ogc-layers``` contains the Darkness layer and it looks like this: 125 | 126 | 127 | 128 | The bands correspond to the following twilight categories: 129 | 130 | * Civil (the lightest) 131 | * Nautical 132 | * Astronomical 133 | * Night (the darkest) 134 | 135 | The layer is visible in the demo. 136 | 137 | The ```grumpy-app``` artifact is a war artifact that can be deployed to a java servlet container (tomcat, glassfish, jetty, jboss, others) to offer a WMS service with the Darkness layer (and any other layers that get added to ```grumpy-ogc-layers```). 138 | 139 | Why Grumpy? 140 | --------------- 141 | The project name was chosen at random and is no hint at the disposition of the primary developer! I'm very happy to receive contributions on this project. Just raise an issue. 142 | -------------------------------------------------------------------------------- /RELEASE_NOTES.md: -------------------------------------------------------------------------------- 1 | Release Notes 2 | --------------- 3 | ###Version 0.2 ([Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.github.davidmoten%22)) 4 | * many breaking changes 5 | * added builder for capabilities 6 | * added ```Reducer`` to abstract reductionist drawing of regions 7 | * refactored Darkness layer 8 | * added grumpy-app artifact to offer Darkness layer in a war 9 | * simplified Darkness layer 10 | 11 | ###Version 0.1 ([Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.github.davidmoten%22)) 12 | * initial release 13 | -------------------------------------------------------------------------------- /generate-site.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | mvn site 4 | mvn site:stage 5 | cd ../davidmoten.github.io 6 | git pull 7 | mkdir grumpy 8 | cp -r ../grumpy/target/staging/* grumpy/ 9 | git add . 10 | git commit -am "update site reports" 11 | git push 12 | -------------------------------------------------------------------------------- /grumpy-app/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 4.0.0 5 | 6 | 7 | com.github.davidmoten 8 | grumpy 9 | 0.4.9-SNAPSHOT 10 | 11 | 12 | grumpy-app 13 | 14 | ${project.artifactId} 15 | Grumpy WMS war with the layers from grumpy-ogc-layers 16 | war 17 | 18 | http://github.com/davidmoten/grumpy 19 | 20 | 21 | 22 | 23 | com.github.davidmoten 24 | grumpy-ogc-layers 25 | ${project.parent.version} 26 | 27 | 28 | 29 | javax.servlet 30 | servlet-api 31 | 2.5 32 | provided 33 | 34 | 35 | 36 | log4j 37 | log4j 38 | 1.2.17.norce 39 | 40 | 41 | 42 | org.slf4j 43 | slf4j-log4j12 44 | ${slf4j.version} 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | org.apache.maven.plugins 53 | maven-war-plugin 54 | ${war.plugin.version} 55 | 56 | 57 | maven-compiler-plugin 58 | 3.14.0 59 | 60 | ${maven.compiler.target} 61 | ${maven.compiler.target} 62 | 63 | 64 | 65 | org.eclipse.jetty 66 | jetty-maven-plugin 67 | ${jetty.plugin.version} 68 | 69 | 10 70 | foo 71 | 9998 72 | 73 | /${project.artifactId} 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /grumpy-app/src/main/java/com/github/davidmoten/grumpy/wms/app/WmsServlet.java: -------------------------------------------------------------------------------- 1 | package com.github.davidmoten.grumpy.wms.app; 2 | 3 | import java.io.IOException; 4 | 5 | import javax.servlet.ServletException; 6 | import javax.servlet.http.HttpServlet; 7 | import javax.servlet.http.HttpServletRequest; 8 | import javax.servlet.http.HttpServletResponse; 9 | 10 | import com.github.davidmoten.grumpy.wms.Capabilities; 11 | import com.github.davidmoten.grumpy.wms.WmsServletRequestProcessor; 12 | import com.github.davidmoten.grumpy.wms.layer.darkness.DarknessLayer; 13 | 14 | public class WmsServlet extends HttpServlet { 15 | private static final long serialVersionUID = 1518113833457077766L; 16 | 17 | private static final String SERVICE_NAME = "Grumpy"; 18 | private static final String SERVICE_TITLE = "Grumpy"; 19 | private static final String SERVICE_ABSTRACT = "Grumpy WMS layers including Darkness layer"; 20 | 21 | private final WmsServletRequestProcessor processor; 22 | 23 | public WmsServlet() { 24 | 25 | // instantiate the layers 26 | DarknessLayer darkness = new DarknessLayer(); 27 | 28 | // setup the capabilities of the service which will extract features 29 | // from the layers to fill in defaults for the layer fields in generated 30 | // capabilities.xml 31 | Capabilities cap = Capabilities.builder() 32 | // set service name 33 | .serviceName(SERVICE_NAME) 34 | // set service title 35 | .serviceTitle(SERVICE_TITLE) 36 | // set service abstract 37 | .serviceAbstract(SERVICE_ABSTRACT) 38 | // add image format 39 | .imageFormat("image/png") 40 | // add info format 41 | .infoFormat("text/html") 42 | // add darkness layer 43 | .layerFeatures(darkness.getFeatures()) 44 | // build caps 45 | .build(); 46 | 47 | // initialize the request processor 48 | processor = WmsServletRequestProcessor.builder() 49 | // capabilities 50 | .capabilities(cap) 51 | // or use 52 | // .capabilitiesFromClasspath("/wms-capabilities.xml") 53 | // set image cache size 54 | .imageCache(200) 55 | // add darkness, not cached 56 | .addLayer("Darkness", darkness) 57 | // build it up 58 | .build(); 59 | } 60 | 61 | @Override 62 | protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { 63 | 64 | // use the processor to handle requests 65 | processor.doGet(req, resp); 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /grumpy-app/src/main/webapp/WEB-INF/classes/log4j.properties: -------------------------------------------------------------------------------- 1 | log4j.rootLogger= INFO, console 2 | log4j.appender.console=org.apache.log4j.ConsoleAppender 3 | log4j.appender.console.layout=org.apache.log4j.PatternLayout 4 | 5 | # Print the date in ISO 8601 format 6 | #log4j.appender.console.layout.ConversionPattern=%d [%t] %-5p %c - %m%n 7 | log4j.appender.console.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss.SSS} %-5p %c [%t] - %m%n 8 | 9 | # Print only messages of level WARN or above in the package com.foo. 10 | #log4j.logger.com.foo=WARN -------------------------------------------------------------------------------- /grumpy-app/src/main/webapp/WEB-INF/web.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | ${project.artifactId} ${project.version} 6 | 7 | 8 | Wms 9 | com.github.davidmoten.grumpy.wms.app.WmsServlet 10 | 11 | 12 | 13 | Wms 14 | /wms 15 | 16 | 17 | 18 | Map 19 | /map.jsp 20 | 21 | 22 | 23 | Map 24 | /map 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /grumpy-app/src/main/webapp/css/style.css: -------------------------------------------------------------------------------- 1 | /** 2 | * CSS Reset 3 | * From Blueprint reset.css 4 | * http://blueprintcss.googlecode.com 5 | */ 6 | html, body, div, span, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, code, del, dfn, em, img, q, dl, dt, dd, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td {margin:0;padding:0;border:0;font-weight:inherit;font-style:inherit;font-size:100%;font-family:inherit;vertical-align:baseline;} 7 | body {line-height:1.5;} 8 | table {border-collapse:separate;border-spacing:0;} 9 | caption, th, td {text-align:left;font-weight:normal;} 10 | table, td, th {vertical-align:middle;} 11 | blockquote:before, blockquote:after, q:before, q:after {content:"";} 12 | blockquote, q {quotes:"" "";} 13 | a img {border:none;} 14 | 15 | /** 16 | * Basic Typography 17 | */ 18 | body { 19 | font-family: "Lucida Grande", Verdana, Geneva, Lucida, Arial, Helvetica, sans-serif; 20 | font-size: 80%; 21 | color: #222; 22 | background: #fff; 23 | margin: 1em 1.5em; 24 | } 25 | pre, code { 26 | margin: 1.5em 0; 27 | white-space: pre; 28 | } 29 | pre, code { 30 | font: 1em 'andale mono', 'lucida console', monospace; 31 | line-height:1.5; 32 | } 33 | a[href] { 34 | color: #436976; 35 | background-color: transparent; 36 | } 37 | h1, h2, h3, h4, h5, h6 { 38 | color: #003a6b; 39 | background-color: transparent; 40 | font: 100% 'Lucida Grande', Verdana, Geneva, Lucida, Arial, Helvetica, sans-serif; 41 | margin: 0; 42 | padding-top: 0.5em; 43 | } 44 | h1 { 45 | font-size: 130%; 46 | margin-bottom: 0.5em; 47 | border-bottom: 1px solid #fcb100; 48 | } 49 | h2 { 50 | font-size: 120%; 51 | margin-bottom: 0.5em; 52 | border-bottom: 1px solid #aaa; 53 | } 54 | h3 { 55 | font-size: 110%; 56 | margin-bottom: 0.5em; 57 | text-decoration: underline; 58 | } 59 | h4 { 60 | font-size: 100%; 61 | font-weight: bold; 62 | } 63 | h5 { 64 | font-size: 100%; 65 | font-weight: bold; 66 | } 67 | h6 { 68 | font-size: 80%; 69 | font-weight: bold; 70 | } 71 | 72 | .olControlAttribution { 73 | bottom: 5px; 74 | } 75 | 76 | /** 77 | * Map Examples Specific 78 | */ 79 | .fullmap { 80 | position:absolute; 81 | top:0; 82 | left:0; 83 | width:100%; 84 | height:100%; 85 | border: 1px solid #ccc; 86 | } 87 | #tags { 88 | display: none; 89 | } 90 | 91 | #docs p { 92 | margin-bottom: 0.5em; 93 | } 94 | /* mobile specific */ 95 | @media only screen and (max-width: 600px) { 96 | body { 97 | height : 100%; 98 | margin : 0; 99 | padding : 0; 100 | width : 100%; 101 | } 102 | #map { 103 | background : #7391ad; 104 | width : 100%; 105 | } 106 | #map { 107 | position: absolute; 108 | top: 0; 109 | left:0; 110 | border : 0; 111 | height : 100%; 112 | } 113 | #title { 114 | font-size : 1.3em; 115 | line-height : 2em; 116 | text-indent : 1em; 117 | margin : 0; 118 | padding : 0; 119 | } 120 | #docs { 121 | bottom : 0; 122 | padding : 1em; 123 | } 124 | #shortdesc { 125 | color : #aaa; 126 | font-size : 0.8em; 127 | padding : 1em; 128 | text-align : right; 129 | } 130 | #tags { 131 | display : none; 132 | } 133 | } 134 | @media only screen and (orientation: landscape) and (max-width: 600px) { 135 | #shortdesc { 136 | float: right; 137 | width: 25%; 138 | } 139 | #map { 140 | width: 70%; 141 | } 142 | #docs { 143 | font-size: 12px; 144 | } 145 | } 146 | body { 147 | -webkit-text-size-adjust: none; 148 | } 149 | -------------------------------------------------------------------------------- /grumpy-app/src/main/webapp/index.html: -------------------------------------------------------------------------------- 1 | 2 |

WMS Demo

3 | 4 | 9 | 10 | -------------------------------------------------------------------------------- /grumpy-app/src/main/webapp/js/base-layers-3857.js: -------------------------------------------------------------------------------- 1 | var map; 2 | 3 | function init() { 4 | 5 | /////////////////////////////////////////// 6 | // setup the base map with google layers 7 | /////////////////////////////////////////// 8 | 9 | map = new OpenLayers.Map('map', { 10 | projection: 'EPSG:3857', 11 | layers: [ 12 | new OpenLayers.Layer.Google( 13 | "Google Physical", 14 | {type: google.maps.MapTypeId.TERRAIN} 15 | ), 16 | new OpenLayers.Layer.Google( 17 | "Google Streets", // the default 18 | {numZoomLevels: 20} 19 | ), 20 | new OpenLayers.Layer.Google( 21 | "Google Hybrid", 22 | {type: google.maps.MapTypeId.HYBRID, numZoomLevels: 20} 23 | ), 24 | new OpenLayers.Layer.Google( 25 | "Google Satellite", 26 | {type: google.maps.MapTypeId.SATELLITE, numZoomLevels: 22} 27 | ), 28 | new OpenLayers.Layer.OSM( "OpenStreetMap") 29 | ], 30 | center: new OpenLayers.LonLat(149.1,-35.3) 31 | // Google.v3 uses web mercator as projection, so we have to 32 | // transform our coordinates 33 | .transform('EPSG:4326', 'EPSG:3857'), 34 | zoom: 6, 35 | zoomMethod: null 36 | }); 37 | 38 | map.addControl(new OpenLayers.Control.LayerSwitcher()); 39 | 40 | addLayers(map); 41 | } 42 | 43 | -------------------------------------------------------------------------------- /grumpy-app/src/main/webapp/js/base-layers-4326.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | function init(){ 4 | var lon = 140; 5 | var lat = -35; 6 | var zoom = 5; 7 | var map = new OpenLayers.Map('map'); 8 | var layer = new OpenLayers.Layer.WMS( "OpenLayers WMS", 9 | "http://vmap0.tiles.osgeo.org/wms/vmap0", {layers: 'basic'} ); 10 | map.addLayer(layer); 11 | map.setCenter(new OpenLayers.LonLat(lon, lat), zoom); 12 | addLayers(map); 13 | } -------------------------------------------------------------------------------- /grumpy-app/src/main/webapp/js/layers.js: -------------------------------------------------------------------------------- 1 | 2 | function addLayers(map) { 3 | 4 | 5 | /////////////////////////////////////////// 6 | // setup the custom wms layer 7 | /////////////////////////////////////////// 8 | 9 | var wmsUrl = "wms"; 10 | 11 | /////////////////////////////////////////// 12 | // add the Darkness layer 13 | /////////////////////////////////////////// 14 | var layer2 = new OpenLayers.Layer.WMS( "Darkness", 15 | wmsUrl, 16 | {layers: 'Darkness',transparent: "true", format: "image/png",styles:"Standard"}, 17 | {gutter:15,singleTile:true, visibility:true,opacity: 0.5,animationEnabled: true}); 18 | 19 | map.addLayer(layer2); 20 | } 21 | 22 | -------------------------------------------------------------------------------- /grumpy-app/src/main/webapp/map-3857.jsp: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | OpenLayers Google (v3) Layer Example 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | -------------------------------------------------------------------------------- /grumpy-app/src/main/webapp/map-4326.jsp: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | OpenLayers EPSG:4326 Example 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | -------------------------------------------------------------------------------- /grumpy-core/README.md: -------------------------------------------------------------------------------- 1 | grumpy-core 2 | ========= 3 | 4 | 5 | -------------------------------------------------------------------------------- /grumpy-core/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 4.0.0 5 | 6 | 7 | com.github.davidmoten 8 | grumpy 9 | 0.4.9-SNAPSHOT 10 | 11 | 12 | grumpy-core 13 | 14 | ${project.artifactId} 15 | OGC tools including WMS server 16 | jar 17 | 18 | http://github.com/davidmoten/grumpy 19 | 20 | 21 | 22 | 23 | org.apache.commons 24 | commons-math3 25 | 3.6.1 26 | 27 | 28 | 29 | 30 | junit 31 | junit 32 | ${junit.version} 33 | test 34 | 35 | 36 | log4j 37 | log4j 38 | 1.2.17.norce 39 | test 40 | 41 | 42 | org.slf4j 43 | slf4j-api 44 | ${slf4j.version} 45 | test 46 | 47 | 48 | org.slf4j 49 | slf4j-log4j12 50 | ${slf4j.version} 51 | test 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | maven-compiler-plugin 60 | 3.14.0 61 | 62 | ${maven.compiler.target} 63 | ${maven.compiler.target} 64 | 65 | 66 | 67 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /grumpy-core/src/test/resources/log4j.properties: -------------------------------------------------------------------------------- 1 | log4j.rootLogger= INFO, console 2 | log4j.appender.console=org.apache.log4j.ConsoleAppender 3 | log4j.appender.console.layout=org.apache.log4j.PatternLayout 4 | 5 | # Print the date in ISO 8601 format 6 | #log4j.appender.console.layout.ConversionPattern=%d [%t] %-5p %c - %m%n 7 | log4j.appender.console.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss.SSS} %-5p %c - %m%n 8 | 9 | # Print only messages of level WARN or above in the package com.foo. 10 | #log4j.logger.com.foo=WARN -------------------------------------------------------------------------------- /grumpy-ogc-layers/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 4.0.0 5 | 6 | 7 | com.github.davidmoten 8 | grumpy 9 | 0.4.9-SNAPSHOT 10 | 11 | 12 | grumpy-ogc-layers 13 | 14 | ${project.artifactId} 15 | OGC layers including Darkness WMS 16 | jar 17 | 18 | http://github.com/davidmoten/grumpy 19 | 20 | 21 | 22 | 23 | com.github.davidmoten 24 | grumpy-ogc 25 | ${project.parent.version} 26 | 27 | 28 | 29 | 30 | 31 | junit 32 | junit 33 | ${junit.version} 34 | test 35 | 36 | 37 | log4j 38 | log4j 39 | 1.2.17.norce 40 | test 41 | 42 | 43 | org.slf4j 44 | slf4j-log4j12 45 | ${slf4j.version} 46 | test 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | maven-compiler-plugin 55 | 3.14.0 56 | 57 | ${maven.compiler.target} 58 | ${maven.compiler.target} 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | osgeo 67 | Open Source Geospatial Foundation Repository 68 | https://download.osgeo.org/webdav/geotools/ 69 | 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /grumpy-ogc-layers/src/main/java/com/github/davidmoten/grumpy/wms/layer/darkness/DarknessLayer.java: -------------------------------------------------------------------------------- 1 | package com.github.davidmoten.grumpy.wms.layer.darkness; 2 | 3 | import java.awt.Color; 4 | import java.awt.Graphics2D; 5 | import java.awt.Point; 6 | import java.awt.Rectangle; 7 | import java.awt.geom.Ellipse2D; 8 | import java.awt.geom.GeneralPath; 9 | import java.awt.image.BufferedImage; 10 | import java.io.IOException; 11 | import java.util.Date; 12 | import java.util.HashMap; 13 | import java.util.List; 14 | import java.util.Map; 15 | 16 | import javax.imageio.ImageIO; 17 | 18 | import com.github.davidmoten.grumpy.core.Position; 19 | import com.github.davidmoten.grumpy.function.Function; 20 | import com.github.davidmoten.grumpy.projection.Projector; 21 | import com.github.davidmoten.grumpy.wms.Layer; 22 | import com.github.davidmoten.grumpy.wms.LayerFeatures; 23 | import com.github.davidmoten.grumpy.wms.RendererUtil; 24 | import com.github.davidmoten.grumpy.wms.WmsRequest; 25 | import com.github.davidmoten.grumpy.wms.WmsUtil; 26 | import com.github.davidmoten.grumpy.wms.layer.darkness.SunUtil.Twilight; 27 | import com.github.davidmoten.grumpy.wms.reduction.RectangleSampler; 28 | import com.github.davidmoten.grumpy.wms.reduction.RectangleSamplerCorners; 29 | import com.github.davidmoten.grumpy.wms.reduction.Reducer; 30 | import com.github.davidmoten.grumpy.wms.reduction.ValueRenderer; 31 | 32 | /** 33 | * Splits the visible region into rectangles recursively till all sampled points 34 | * in each rectangle have the same {@link Twilight} value. Once the rectangle 35 | * has a uniform {@link Twilight} value it is filled with the shade 36 | * corresponding to the {@link Twilight} value. 37 | * 38 | * @author Steven Ring 39 | * @author Dave Moten 40 | */ 41 | public class DarknessLayer implements Layer { 42 | 43 | private static final String STYLE_PLAIN = "plain"; 44 | private static final int SUB_SOLAR_POINT_SIZE_PIXELS = 30; 45 | private static final Map shades = createShades(); 46 | private final BufferedImage subSolarImage; 47 | private final LayerFeatures features; 48 | 49 | public DarknessLayer() { 50 | subSolarImage = loadSubSolarPointImage(); 51 | features = LayerFeatures // 52 | .builder() // 53 | .name("Darkness") // 54 | .style(STYLE_PLAIN) // 55 | .crs("EPSG:4326") // 56 | .crs("EPSG:3857") // 57 | .build(); 58 | } 59 | 60 | @Override 61 | public void render(Graphics2D g, WmsRequest request) { 62 | Projector projector = WmsUtil.getProjector(request); 63 | Position subSolarPoint = SunUtil.getSubSolarPoint(); 64 | renderSubSolarPoint(g, subSolarPoint, projector, subSolarImage, request.getStyles()); 65 | renderTwilight(g, subSolarPoint, projector); 66 | } 67 | 68 | private static void renderSubSolarPoint(Graphics2D g, Position subSolarPoint, 69 | Projector projector, BufferedImage subSolarImage, List styles) { 70 | 71 | Point point = projector.toPoint(subSolarPoint.getLat(), subSolarPoint.getLon()); 72 | int size = SUB_SOLAR_POINT_SIZE_PIXELS; 73 | if (styles.contains(STYLE_PLAIN)) { 74 | fillCircle(g, point, size); 75 | } else 76 | g.drawImage(subSolarImage, point.x - size / 2, point.y - size / 2, size, size, null); 77 | } 78 | 79 | private static void fillCircle(Graphics2D g, Point point, int size) { 80 | Ellipse2D spot = new Ellipse2D.Double(); 81 | g.setColor(Color.YELLOW); 82 | spot.setFrame(point.x - size / 2, point.y - size / 2, size, size); 83 | g.fill(spot); 84 | } 85 | 86 | private static BufferedImage loadSubSolarPointImage() { 87 | try { 88 | return ImageIO.read(DarknessLayer.class.getResourceAsStream("/sunny.png")); 89 | } catch (IOException e) { 90 | throw new RuntimeException(e); 91 | } 92 | } 93 | 94 | private static void renderTwilight(Graphics2D g, final Position subSolarPoint, 95 | Projector projector) { 96 | 97 | Function function = createValueFunction(subSolarPoint); 98 | ValueRenderer valueRenderer = createValueRenderer(); 99 | RectangleSampler sampler = new RectangleSamplerCorners(); 100 | Reducer.render(g, function, projector, sampler, valueRenderer); 101 | } 102 | 103 | private static Function createValueFunction(final Position subSolarPoint) { 104 | return new Function() { 105 | @Override 106 | public Twilight apply(Position p) { 107 | return SunUtil.getTwilight(subSolarPoint, p); 108 | } 109 | }; 110 | } 111 | 112 | private static ValueRenderer createValueRenderer() { 113 | return new ValueRenderer() { 114 | @Override 115 | public void render(Graphics2D g, Projector projector, Rectangle region, Twilight t) { 116 | renderBounds(g, projector, region, t); 117 | } 118 | }; 119 | } 120 | 121 | private static void renderBounds(Graphics2D g, Projector projector, Rectangle region, 122 | final Twilight twilight) { 123 | if (twilight != Twilight.DAYLIGHT) { 124 | 125 | List box = WmsUtil.getBorder(projector, region); 126 | 127 | // use multiple paths to handle boundary weirdness 128 | List path = RendererUtil.toPath(projector, box); 129 | 130 | // fill the region 131 | g.setColor(shades.get(twilight)); 132 | RendererUtil.fill(g, path); 133 | } 134 | } 135 | 136 | private static Map createShades() { 137 | Map shades = new HashMap(); 138 | shades.put(Twilight.NIGHT, Color.BLACK); 139 | shades.put(Twilight.ASTRONOMICAL, new Color(50, 50, 50)); 140 | shades.put(Twilight.NAUTICAL, new Color(100, 100, 100)); 141 | shades.put(Twilight.CIVIL, new Color(150, 150, 150)); 142 | shades.put(Twilight.DAYLIGHT, Color.WHITE); 143 | return shades; 144 | } 145 | 146 | @Override 147 | public String getInfo(Date time, WmsRequest request, Point point, String format) { 148 | return null; 149 | } 150 | 151 | @Override 152 | public LayerFeatures getFeatures() { 153 | return features; 154 | } 155 | 156 | } -------------------------------------------------------------------------------- /grumpy-ogc-layers/src/main/java/com/github/davidmoten/grumpy/wms/layer/darkness/SunUtil.java: -------------------------------------------------------------------------------- 1 | package com.github.davidmoten.grumpy.wms.layer.darkness; 2 | 3 | import java.util.Calendar; 4 | import java.util.GregorianCalendar; 5 | import java.util.TimeZone; 6 | 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | 10 | import com.github.davidmoten.grumpy.core.Position; 11 | 12 | /** 13 | * Utility methods related to the position of the Sun relative to the Earth. 14 | * 15 | * @author Steven Ring 16 | * 17 | */ 18 | public final class SunUtil { 19 | 20 | private static Logger log = LoggerFactory.getLogger(SunUtil.class); 21 | private static final double EARTH_RADIUS_KM = 6378.0; 22 | 23 | /** 24 | * Constructor to prevent inheritance 25 | */ 26 | private SunUtil() { 27 | } 28 | 29 | public static enum Twilight { 30 | NIGHT, ASTRONOMICAL, NAUTICAL, CIVIL, DAYLIGHT 31 | } 32 | 33 | /** 34 | * Return the twilight condition for a point which is a given great circle 35 | * distance from the current sub solar point. 36 | * 37 | * 38 | * @param sunDistanceRadians 39 | * - from the current positon to the sub solar point 40 | * @return The twilight condition 41 | */ 42 | public static Twilight getTwilight(double sunDistanceRadians) { 43 | 44 | double altDegrees = 90.0 - Math.toDegrees(sunDistanceRadians); 45 | if (altDegrees >= 0.0) { 46 | return Twilight.DAYLIGHT; 47 | } else if (altDegrees >= -6.0) { 48 | return Twilight.CIVIL; 49 | } else if (altDegrees >= -12.0) { 50 | return Twilight.NAUTICAL; 51 | } else if (altDegrees >= -18.0) { 52 | return Twilight.ASTRONOMICAL; 53 | } 54 | 55 | return Twilight.NIGHT; 56 | } 57 | 58 | public static Twilight getTwilight(Position subSolarPoint, 59 | Position somePosition) { 60 | double distKm = somePosition.getDistanceToKm(subSolarPoint); 61 | double distRads = distKm / EARTH_RADIUS_KM; 62 | return getTwilight(distRads); 63 | } 64 | 65 | /** 66 | * Gets the position of the Sun right now. 67 | * 68 | * @return position of the sun now 69 | */ 70 | public static Position getSubSolarPoint() { 71 | return getSubSolarPoint(GregorianCalendar.getInstance(TimeZone 72 | .getTimeZone("GMT"))); 73 | } 74 | 75 | /** 76 | * Returns the position on the Earth's surface for which the sun appears to 77 | * be straight above. 78 | * 79 | * @param time 80 | * @return position of the sub-solar point 81 | */ 82 | public static Position getSubSolarPoint(Calendar time) { 83 | 84 | // convert time to Julian Day Number 85 | 86 | double jd = TimeUtil.getJulianDayNumber(time); 87 | // Julian centuries since Jan 1, 2000, 12:00 UTC 88 | 89 | double T = (jd - 2451545.0) / 36525; 90 | 91 | // mean anomaly, degree 92 | double M = 357.52910 + 35999.05030 * T - 0.0001559 * T * T - 0.00000048 93 | * T * T * T; 94 | 95 | // mean longitude, degree 96 | double L0 = 280.46645 + 36000.76983 * T + 0.0003032 * T * T; 97 | 98 | double DL = (1.914600 - 0.004817 * T - 0.000014 * T * T) 99 | * Math.sin(Math.toRadians(M)) + (0.019993 - 0.000101 * T) 100 | * Math.sin(Math.toRadians(2.0 * M)) + 0.000290 101 | * Math.sin(Math.toRadians(3.0 * M)); 102 | 103 | // true longitude, degree 104 | double L = L0 + DL; 105 | 106 | // obliquity eps of ecliptic in degrees: 107 | double eps = 23.0 + 26.0 / 60.0 + 21.448 / 3600.0 108 | - (46.8150 * T + 0.00059 * T * T - 0.001813 * T * T * T) 109 | / 3600.0; 110 | 111 | double X = Math.cos(Math.toRadians(L)); 112 | double Y = Math.cos(Math.toRadians(eps)) * Math.sin(Math.toRadians(L)); 113 | double Z = Math.sin(Math.toRadians(eps)) * Math.sin(Math.toRadians(L)); 114 | double R = Math.sqrt(1.0 - Z * Z); 115 | 116 | double delta = Math.toDegrees(Math.atan(Z / R)); // in degrees 117 | double p = Y / (X + R); 118 | double ra = Math.toDegrees(Math.atan(p)); 119 | double RA = (24.0 / 180.0) * ra; // in hours 120 | 121 | // sidereal time (in hours) 122 | 123 | double theta0 = 280.46061837 + 360.98564736629 * (jd - 2451545.0) 124 | + 0.000387933 * T * T - T * T * T / 38710000.0; 125 | double sidTime = (theta0 % 360) / 15.0; 126 | 127 | // lon and lat of sun 128 | 129 | double sunHADeg = ((sidTime - RA) * 15.0) % 360.0; 130 | double lon = 0.0; 131 | if (sunHADeg < 180.0) { 132 | lon = -sunHADeg; 133 | } else { 134 | lon = 360.0 - sunHADeg; 135 | } 136 | double lat = delta; 137 | 138 | log.info("Sidereal time is " + sidTime + ", Sun RA/Dec is " + RA + "/" 139 | + delta + ", subSolar lat/long is " + lat + "/" + lon); 140 | 141 | return new Position(lat, lon); 142 | 143 | } 144 | 145 | } -------------------------------------------------------------------------------- /grumpy-ogc-layers/src/main/java/com/github/davidmoten/grumpy/wms/layer/darkness/TimeUtil.java: -------------------------------------------------------------------------------- 1 | package com.github.davidmoten.grumpy.wms.layer.darkness; 2 | 3 | import java.util.Calendar; 4 | import java.util.GregorianCalendar; 5 | import java.util.TimeZone; 6 | import java.util.concurrent.TimeUnit; 7 | 8 | /** 9 | * Utility class to help with time-based calculations in Astronomy 10 | * 11 | * @author Steven Ring 12 | * 13 | */ 14 | public final class TimeUtil { 15 | 16 | private static final double MILLISEC_PER_DAY = TimeUnit.DAYS.toMillis(1); 17 | public static final double BASE_JD = 2440587.5; 18 | 19 | /** 20 | * Constructor to prevent inheritance 21 | */ 22 | private TimeUtil() { 23 | } 24 | 25 | /** 26 | * Calculate the Julian day number corresponding to the time provided 27 | * 28 | * @param time 29 | * , the time for which the Julian Day number is required. If 30 | * null, the current time will be used. 31 | * 32 | * @return - the Julian day number corresponding to the supplied time 33 | */ 34 | public static double getJulianDayNumber(Calendar time) { 35 | 36 | if (time == null) { 37 | time = GregorianCalendar.getInstance(TimeZone.getTimeZone("GMT")); 38 | } 39 | 40 | double jd = BASE_JD + time.getTimeInMillis() / MILLISEC_PER_DAY; 41 | 42 | return jd; 43 | } 44 | 45 | } -------------------------------------------------------------------------------- /grumpy-ogc-layers/src/main/resources/sunny.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidmoten/grumpy/7a956d47a15e53420afc64e4d9999a20814687d3/grumpy-ogc-layers/src/main/resources/sunny.png -------------------------------------------------------------------------------- /grumpy-ogc/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 4.0.0 5 | 6 | 7 | com.github.davidmoten 8 | grumpy 9 | 0.4.9-SNAPSHOT 10 | 11 | 12 | grumpy-ogc 13 | 14 | ${project.artifactId} 15 | OGC tools including WMS server 16 | jar 17 | 18 | http://github.com/davidmoten/grumpy 19 | 20 | 21 | 22 | 23 | com.github.davidmoten 24 | grumpy-projection 25 | ${project.parent.version} 26 | 27 | 28 | 29 | com.github.davidmoten 30 | guava-mini 31 | ${guava.mini.version} 32 | 33 | 34 | 35 | javax.servlet 36 | servlet-api 37 | 2.5 38 | provided 39 | 40 | 41 | 42 | org.apache.commons 43 | commons-lang3 44 | 3.17.0 45 | 46 | 47 | 48 | com.jamesmurty.utils 49 | java-xmlbuilder 50 | 1.3 51 | 52 | 53 | 54 | 55 | junit 56 | junit 57 | ${junit.version} 58 | test 59 | 60 | 61 | log4j 62 | log4j 63 | 1.2.17.norce 64 | test 65 | 66 | 67 | org.slf4j 68 | slf4j-log4j12 69 | ${slf4j.version} 70 | test 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | maven-compiler-plugin 79 | 3.14.0 80 | 81 | ${maven.compiler.target} 82 | ${maven.compiler.target} 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | osgeo 91 | Open Source Geospatial Foundation Repository 92 | https://download.osgeo.org/webdav/geotools/ 93 | 94 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /grumpy-ogc/src/main/java/com/github/davidmoten/grumpy/function/Function.java: -------------------------------------------------------------------------------- 1 | package com.github.davidmoten.grumpy.function; 2 | 3 | @FunctionalInterface 4 | public interface Function { 5 | 6 | R apply(T value); 7 | 8 | } 9 | -------------------------------------------------------------------------------- /grumpy-ogc/src/main/java/com/github/davidmoten/grumpy/wms/Capabilities.java: -------------------------------------------------------------------------------- 1 | package com.github.davidmoten.grumpy.wms; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import com.github.davidmoten.guavamini.Preconditions; 7 | 8 | public final class Capabilities { 9 | private final String serviceName; 10 | private final String serviceTitle; 11 | private final String serviceAbstract; 12 | private final Integer serviceMaxWidth; 13 | private final Integer serviceMaxHeight; 14 | private final List imageFormats; 15 | private final List infoFormats; 16 | private final List layers; 17 | private final String serviceUrlBase; 18 | 19 | private Capabilities(String serviceName, String serviceTitle, String serviceAbstract, 20 | Integer serviceMaxWidth, Integer serviceMaxHeight, List imageFormats, 21 | List infoFormats, List layers, String serviceUrlBase) { 22 | Preconditions.checkNotNull(serviceName, "serviceName cannot be null"); 23 | Preconditions.checkNotNull(imageFormats, "imageFormats cannot be null"); 24 | Preconditions.checkNotNull(infoFormats, "infoFormats cannot be null"); 25 | Preconditions.checkNotNull(layers, "layers cannot be null"); 26 | Preconditions.checkNotNull(serviceUrlBase, "serviceUrlBase cannot be null"); 27 | this.serviceName = serviceName; 28 | this.serviceTitle = nvl(serviceTitle, serviceName); 29 | this.serviceAbstract = nvl(serviceAbstract, serviceName); 30 | this.serviceMaxWidth = serviceMaxWidth; 31 | this.serviceMaxHeight = serviceMaxHeight; 32 | this.imageFormats = imageFormats; 33 | this.infoFormats = infoFormats; 34 | this.layers = layers; 35 | this.serviceUrlBase = serviceUrlBase; 36 | } 37 | 38 | private static T nvl(T v, T w) { 39 | if (v == null) { 40 | return w; 41 | } else { 42 | return v; 43 | } 44 | } 45 | 46 | public String getServiceTitle() { 47 | return serviceTitle; 48 | } 49 | 50 | public String getServiceName() { 51 | return serviceName; 52 | } 53 | 54 | public String getServiceAbstract() { 55 | return serviceAbstract; 56 | } 57 | 58 | public Integer getServiceMaxWidth() { 59 | return serviceMaxWidth; 60 | } 61 | 62 | public Integer getServiceMaxHeight() { 63 | return serviceMaxHeight; 64 | } 65 | 66 | public List getImageFormats() { 67 | return imageFormats; 68 | } 69 | 70 | public List getInfoFormats() { 71 | return infoFormats; 72 | } 73 | 74 | public List getLayers() { 75 | return layers; 76 | } 77 | 78 | public String getServiceUrlBase() { 79 | return serviceUrlBase; 80 | } 81 | 82 | public static Builder builder() { 83 | return new Builder(); 84 | } 85 | 86 | public static class Builder { 87 | 88 | private String serviceName; 89 | private String serviceTitle; 90 | private String serviceAbstract; 91 | private Integer serviceMaxWidth = 2000; 92 | private Integer serviceMaxHeight = 2000; 93 | private List imageFormats = new ArrayList(); 94 | private List infoFormats = new ArrayList(); 95 | private List layers = new ArrayList(); 96 | private String serviceUrlBase; 97 | 98 | private Builder() { 99 | } 100 | 101 | public Builder serviceBaseUrl(String serviceUrlBase) { 102 | this.serviceUrlBase = serviceUrlBase; 103 | return this; 104 | } 105 | 106 | public Builder serviceName(String serviceName) { 107 | this.serviceName = serviceName; 108 | return this; 109 | } 110 | 111 | public Builder serviceTitle(String serviceTitle) { 112 | this.serviceTitle = serviceTitle; 113 | return this; 114 | } 115 | 116 | public Builder serviceAbstract(String serviceAbstract) { 117 | this.serviceAbstract = serviceAbstract; 118 | return this; 119 | } 120 | 121 | public Builder serviceMaxWidth(Integer serviceMaxWidth) { 122 | this.serviceMaxWidth = serviceMaxWidth; 123 | return this; 124 | } 125 | 126 | public Builder serviceMaxHeight(Integer serviceMaxHeight) { 127 | this.serviceMaxHeight = serviceMaxHeight; 128 | return this; 129 | } 130 | 131 | public Builder imageFormats(List imageFormats) { 132 | this.imageFormats = imageFormats; 133 | return this; 134 | } 135 | 136 | public Builder imageFormat(String imageFormat) { 137 | this.imageFormats.add(imageFormat); 138 | return this; 139 | } 140 | 141 | public Builder infoFormats(List infoFormats) { 142 | this.infoFormats = infoFormats; 143 | return this; 144 | } 145 | 146 | public Builder infoFormat(String infoFormat) { 147 | this.infoFormats.add(infoFormat); 148 | return this; 149 | } 150 | 151 | public Builder layers(List layers) { 152 | this.layers = layers; 153 | return this; 154 | } 155 | 156 | public Builder layer(CapabilitiesLayer layer) { 157 | this.layers.add(layer); 158 | return this; 159 | } 160 | 161 | public Builder layerFeatures(LayerFeatures layerFeatures) { 162 | this.layers.add(CapabilitiesLayer.from(layerFeatures).build()); 163 | return this; 164 | } 165 | 166 | public Builder layerFeatures(Layer layer) { 167 | return layerFeatures(layer.getFeatures()); 168 | } 169 | 170 | public Capabilities build() { 171 | if (serviceTitle == null) 172 | serviceTitle = serviceName; 173 | if (serviceAbstract == null) 174 | serviceAbstract = serviceName; 175 | return new Capabilities(serviceName, serviceTitle, serviceAbstract, serviceMaxWidth, 176 | serviceMaxHeight, imageFormats, infoFormats, layers, serviceUrlBase); 177 | } 178 | } 179 | 180 | } 181 | -------------------------------------------------------------------------------- /grumpy-ogc/src/main/java/com/github/davidmoten/grumpy/wms/CapabilitiesLayer.java: -------------------------------------------------------------------------------- 1 | package com.github.davidmoten.grumpy.wms; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | public final class CapabilitiesLayer { 7 | 8 | private final String name; 9 | private final String title; 10 | private final boolean queryable; 11 | private final boolean opaque; 12 | private final List crs; 13 | private final List styles; 14 | private final List layers; 15 | 16 | private CapabilitiesLayer(String name, String title, boolean queryable, boolean opaque, 17 | List crs, List styles, List layers) { 18 | this.name = name; 19 | this.title = title; 20 | this.crs = crs; 21 | this.styles = styles; 22 | this.layers = layers; 23 | this.queryable = queryable; 24 | this.opaque = opaque; 25 | } 26 | 27 | public boolean isQueryable() { 28 | return queryable; 29 | } 30 | 31 | public boolean isOpaque() { 32 | return opaque; 33 | } 34 | 35 | public String getName() { 36 | return name; 37 | } 38 | 39 | public String getTitle() { 40 | return title; 41 | } 42 | 43 | public List getCrs() { 44 | return crs; 45 | } 46 | 47 | public List getStyles() { 48 | return styles; 49 | } 50 | 51 | public List getLayers() { 52 | return layers; 53 | } 54 | 55 | public static Builder builder() { 56 | return new Builder(); 57 | } 58 | 59 | public static Builder from(LayerFeatures layer) { 60 | return new Builder().layerFeatures(layer); 61 | } 62 | 63 | public static class Builder { 64 | 65 | private String name; 66 | private String title; 67 | private List crs = new ArrayList(); 68 | private List styles = new ArrayList(); 69 | private List layers = new ArrayList(); 70 | private Boolean queryable = null; 71 | private boolean opaque = true; 72 | private LayerFeatures layerFeatures; 73 | 74 | private Builder() { 75 | } 76 | 77 | public Builder name(String name) { 78 | this.name = name; 79 | return this; 80 | } 81 | 82 | public Builder title(String title) { 83 | this.title = title; 84 | return this; 85 | } 86 | 87 | public Builder queryable(boolean value) { 88 | this.queryable = value; 89 | return this; 90 | } 91 | 92 | public Builder opaque(boolean value) { 93 | this.opaque = value; 94 | return this; 95 | } 96 | 97 | public Builder queryable() { 98 | return queryable(true); 99 | } 100 | 101 | public Builder opaque() { 102 | return opaque(true); 103 | } 104 | 105 | public Builder crs(List crs) { 106 | this.crs = crs; 107 | return this; 108 | } 109 | 110 | public Builder crs(String crs) { 111 | this.crs.add(crs); 112 | return this; 113 | } 114 | 115 | public Builder styles(List styles) { 116 | this.styles = styles; 117 | return this; 118 | } 119 | 120 | public Builder style(String style) { 121 | this.styles.add(style); 122 | return this; 123 | } 124 | 125 | public Builder layerFeatures(LayerFeatures layerFeatures) { 126 | this.layerFeatures = layerFeatures; 127 | return this; 128 | } 129 | 130 | public Builder layers(List layers) { 131 | this.layers = layers; 132 | return this; 133 | } 134 | 135 | public Builder layer(CapabilitiesLayer layer) { 136 | this.layers.add(layer); 137 | return this; 138 | } 139 | 140 | public CapabilitiesLayer build() { 141 | if (layerFeatures != null) { 142 | styles.addAll(layerFeatures.getStyles()); 143 | crs.addAll(layerFeatures.getCrs()); 144 | if (name == null) 145 | name = layerFeatures.getName(); 146 | if (queryable == null) 147 | queryable = layerFeatures.isQueryable(); 148 | } 149 | if (title == null) 150 | title = name; 151 | if (queryable == null) 152 | queryable = false; 153 | return new CapabilitiesLayer(name, title, queryable, opaque, crs, styles, layers); 154 | } 155 | } 156 | 157 | } 158 | -------------------------------------------------------------------------------- /grumpy-ogc/src/main/java/com/github/davidmoten/grumpy/wms/CapabilitiesProvider.java: -------------------------------------------------------------------------------- 1 | package com.github.davidmoten.grumpy.wms; 2 | 3 | import javax.servlet.http.HttpServletRequest; 4 | 5 | /** 6 | * Provides the response to the WMS GetCapabilities request. 7 | */ 8 | public interface CapabilitiesProvider { 9 | String getCapabilities(HttpServletRequest request); 10 | } 11 | -------------------------------------------------------------------------------- /grumpy-ogc/src/main/java/com/github/davidmoten/grumpy/wms/CapabilitiesProviderEmpty.java: -------------------------------------------------------------------------------- 1 | package com.github.davidmoten.grumpy.wms; 2 | 3 | import javax.servlet.http.HttpServletRequest; 4 | 5 | public final class CapabilitiesProviderEmpty implements CapabilitiesProvider { 6 | 7 | private final CapabilitiesProvider provider = CapabilitiesProviderFromClasspath 8 | .fromClasspath("/wms-capabilities-empty.xml"); 9 | 10 | @Override 11 | public String getCapabilities(HttpServletRequest request) { 12 | return provider.getCapabilities(request); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /grumpy-ogc/src/main/java/com/github/davidmoten/grumpy/wms/CapabilitiesProviderFromCapabilities.java: -------------------------------------------------------------------------------- 1 | package com.github.davidmoten.grumpy.wms; 2 | 3 | import java.io.IOException; 4 | import java.io.InputStream; 5 | import java.io.UncheckedIOException; 6 | import java.nio.charset.StandardCharsets; 7 | import java.util.List; 8 | 9 | import javax.servlet.http.HttpServletRequest; 10 | import javax.xml.parsers.FactoryConfigurationError; 11 | import javax.xml.parsers.ParserConfigurationException; 12 | import javax.xml.transform.TransformerException; 13 | 14 | import org.apache.commons.io.IOUtils; 15 | import org.apache.commons.text.StringEscapeUtils; 16 | import org.slf4j.Logger; 17 | import org.slf4j.LoggerFactory; 18 | 19 | import com.jamesmurty.utils.XMLBuilder; 20 | 21 | public final class CapabilitiesProviderFromCapabilities implements CapabilitiesProvider { 22 | 23 | private static final Logger log = LoggerFactory 24 | .getLogger(CapabilitiesProviderFromCapabilities.class); 25 | 26 | private final Capabilities capabilities; 27 | 28 | public CapabilitiesProviderFromCapabilities(Capabilities capabilities) { 29 | this.capabilities = capabilities; 30 | 31 | } 32 | 33 | @Override 34 | public String getCapabilities(HttpServletRequest request) { 35 | String template = getTemplate(); 36 | template = template.replace("${serviceName}", capabilities.getServiceName()); 37 | template = template.replace("${serviceTitle}", capabilities.getServiceTitle()); 38 | template = template.replace("${serviceAbstract}", capabilities.getServiceAbstract()); 39 | template = template.replace("${serviceMaxWidth}", capabilities.getServiceMaxWidth() + ""); 40 | template = template.replace("${serviceMaxHeight}", capabilities.getServiceMaxHeight() + ""); 41 | template = template.replace("${imageFormats}", formats(capabilities.getImageFormats())); 42 | template = template.replace("${infoFormats}", formats(capabilities.getInfoFormats())); 43 | template = template.replace("${layers}", layers(capabilities.getLayers())); 44 | template = template.replace("${serviceBaseUrl}", 45 | StringEscapeUtils.escapeXml11(adjustUrl(capabilities.getServiceUrlBase()))); 46 | log.info("capabilities=\n" + template); 47 | return template; 48 | } 49 | 50 | private static final String SERVICE_EQUALS_WMS = "SERVICE=WMS"; 51 | 52 | private static String adjustUrl(String u) { 53 | // ensure ends in & and contains parameter SERVICE=WMS 54 | if (u.contains("?")) { 55 | if (u.contains(SERVICE_EQUALS_WMS)) { 56 | if (u.endsWith("&")) { 57 | return u; 58 | } else { 59 | return u + "&"; 60 | } 61 | } else if (u.endsWith("&")) { 62 | return u + SERVICE_EQUALS_WMS + "&"; 63 | } else { 64 | return u + "&" + SERVICE_EQUALS_WMS + "&"; 65 | } 66 | } else { 67 | return u + "?" + SERVICE_EQUALS_WMS + "&"; 68 | } 69 | } 70 | 71 | private String layers(List layers) { 72 | StringBuilder s = new StringBuilder(); 73 | for (CapabilitiesLayer layer : layers) { 74 | s.append(layer(layer)); 75 | s.append("\n\n"); 76 | } 77 | return s.toString(); 78 | } 79 | 80 | private String layer(CapabilitiesLayer layer) { 81 | try { 82 | XMLBuilder xml = XMLBuilder.create("Layer"); 83 | if (layer.isQueryable()) 84 | xml = xml.a("queryable", "1"); 85 | if (layer.isOpaque()) 86 | xml = xml.a("opaque", "1"); 87 | xml = xml.element("Name").text(layer.getName()).up() 88 | // add title 89 | .element("Title").text(layer.getTitle()).up(); 90 | for (String crs : layer.getCrs()) { 91 | xml = xml.element("CRS").text(crs).up(); 92 | } 93 | 94 | xml = xml.e("EX_GeographicBoundingBox") // 95 | .element("westBoundLongitude").text("-180").up() // 96 | .element("eastBoundLongitude").text("180").up() // 97 | .element("southBoundLatitude").text("-90").up() // 98 | .element("northBoundLatitude").text("90").up() // 99 | .up(); 100 | // wms 1.3.0 expects lat, long order in coordinates 101 | xml = xml.e("BoundingBox") // 102 | .a("CRS", "EPSG:4326") // 103 | .a("minx", "-90") // 104 | .a("miny", "-180") // 105 | .a("maxx", "90") // 106 | .a("maxy", "180") // 107 | .up(); 108 | // xml = xml.e("MaxScaleDenominator").text("500000000").up(); 109 | for (String style : layer.getStyles()) { 110 | xml = xml.element("Style").element("Name").text(style).up().up(); 111 | } 112 | return xml.asString(); 113 | } catch (ParserConfigurationException e) { 114 | throw new RuntimeException(e); 115 | } catch (FactoryConfigurationError e) { 116 | throw new RuntimeException(e); 117 | } catch (TransformerException e) { 118 | throw new RuntimeException(e); 119 | } 120 | } 121 | 122 | private String formats(List formats) { 123 | StringBuilder s = new StringBuilder(); 124 | for (String format : formats) { 125 | s.append("" + format + ""); 126 | } 127 | return s.toString(); 128 | } 129 | 130 | private static String getTemplate() { 131 | try (InputStream in = CapabilitiesProviderFromCapabilities.class 132 | .getResourceAsStream("/wms-capabilities-template.xml")) { 133 | return IOUtils.toString(in, StandardCharsets.UTF_8); 134 | } catch (IOException e) { 135 | throw new UncheckedIOException(e); 136 | } 137 | } 138 | 139 | } 140 | -------------------------------------------------------------------------------- /grumpy-ogc/src/main/java/com/github/davidmoten/grumpy/wms/CapabilitiesProviderFromClasspath.java: -------------------------------------------------------------------------------- 1 | package com.github.davidmoten.grumpy.wms; 2 | 3 | import java.io.IOException; 4 | import java.io.InputStream; 5 | import java.io.UncheckedIOException; 6 | import java.nio.charset.StandardCharsets; 7 | 8 | import javax.servlet.http.HttpServletRequest; 9 | 10 | import org.apache.commons.io.IOUtils; 11 | 12 | public final class CapabilitiesProviderFromClasspath implements CapabilitiesProvider { 13 | 14 | private final String resource; 15 | 16 | public CapabilitiesProviderFromClasspath(String resource) { 17 | this.resource = resource; 18 | } 19 | 20 | public static CapabilitiesProvider fromClasspath(String resource) { 21 | return new CapabilitiesProviderFromClasspath(resource); 22 | } 23 | 24 | @Override 25 | public String getCapabilities(HttpServletRequest request) { 26 | try (InputStream in = CapabilitiesProviderFromClasspath.class 27 | .getResourceAsStream(resource)) { 28 | return IOUtils.toString(in, StandardCharsets.UTF_8); 29 | } catch (IOException e) { 30 | throw new UncheckedIOException(e); 31 | } 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /grumpy-ogc/src/main/java/com/github/davidmoten/grumpy/wms/HasLayerFeatures.java: -------------------------------------------------------------------------------- 1 | package com.github.davidmoten.grumpy.wms; 2 | 3 | public interface HasLayerFeatures { 4 | 5 | /** 6 | * Returns features about the WMS layer including styles, supported CRS and 7 | * the default name of the layer. 8 | * 9 | * @return 10 | */ 11 | LayerFeatures getFeatures(); 12 | 13 | } 14 | -------------------------------------------------------------------------------- /grumpy-ogc/src/main/java/com/github/davidmoten/grumpy/wms/ImageCache.java: -------------------------------------------------------------------------------- 1 | package com.github.davidmoten.grumpy.wms; 2 | 3 | import java.util.ArrayList; 4 | import java.util.HashSet; 5 | import java.util.List; 6 | import java.util.Map; 7 | import java.util.Set; 8 | import java.util.concurrent.ConcurrentHashMap; 9 | 10 | import org.slf4j.Logger; 11 | import org.slf4j.LoggerFactory; 12 | 13 | /** 14 | * Caches images keyed on the {@link WmsRequest}. 15 | * 16 | * @author dxm 17 | * 18 | */ 19 | public class ImageCache { 20 | 21 | private static Logger log = LoggerFactory.getLogger(ImageCache.class); 22 | 23 | private static int DEFAULT_SIZE = 250;// 50MB at 200K per image 24 | 25 | /** 26 | * Records the keys and the order they went into the cache so we can trim 27 | * the cache if needed. 28 | */ 29 | private volatile List keys = new ArrayList(); 30 | 31 | private volatile Set layers = new HashSet(); 32 | 33 | private volatile Map cache = new ConcurrentHashMap(); 34 | 35 | private final int maxSize; 36 | 37 | /** 38 | * Constructor. 39 | */ 40 | public ImageCache() { 41 | this(DEFAULT_SIZE); 42 | } 43 | 44 | /** 45 | * Constructor. 46 | * 47 | * @param size 48 | * the maximum number of elements in the cache 49 | */ 50 | public ImageCache(int size) { 51 | this.maxSize = size; 52 | } 53 | 54 | /** 55 | * Factory method. Returns a new {@link ImageCache} of given maximum size. 56 | * 57 | * @param size 58 | * @return 59 | */ 60 | public static ImageCache create(int size) { 61 | return new ImageCache(size); 62 | } 63 | 64 | /** 65 | * Clears the cache for the given layer name. 66 | * 67 | * @param layerName 68 | */ 69 | // TOOD improve this 70 | public void clear(String layerName) { 71 | synchronized (this) { 72 | log.info("clearing cache for layer " + layerName); 73 | for (String key : cache.keySet()) { 74 | if (key.contains(layerName)) 75 | remove(key); 76 | } 77 | } 78 | } 79 | 80 | private void remove(String key) { 81 | cache.remove(key); 82 | log.info("removed cache entry " + key); 83 | } 84 | 85 | /** 86 | * Clears the cache. 87 | */ 88 | public void clear() { 89 | synchronized (this) { 90 | cache.clear(); 91 | } 92 | } 93 | 94 | /** 95 | * Enables/disables a layer for caching. 96 | * 97 | * @param layerName 98 | * name of the WMS layer 99 | * @param enabled 100 | * is true if want to cache 101 | */ 102 | public void setEnabled(String layerName, boolean enabled) { 103 | synchronized (this) { 104 | if (enabled) 105 | layers.add(layerName); 106 | else 107 | layers.remove(layerName); 108 | } 109 | } 110 | 111 | private static String getKey(WmsRequest request) { 112 | StringBuffer s = new StringBuffer(); 113 | for (String name : request.getParameterNames()) 114 | // make sure we exclude the _OLSALT parameter which changes with 115 | // every request 116 | if (!name.startsWith("_")) 117 | add(s, name, request.getParam(name)); 118 | return s.toString(); 119 | } 120 | 121 | private static void add(StringBuffer s, String name, Object value) { 122 | s.append(name); 123 | s.append("="); 124 | s.append(String.valueOf(value)); 125 | s.append(";"); 126 | } 127 | 128 | /** 129 | * Get the bytes of the image returned by a {@link WmsRequest}. Returns null 130 | * if no corresponding image exists in the cache. 131 | * 132 | * @param request 133 | * the WMS http request 134 | * @return bytes of the image 135 | */ 136 | public byte[] get(WmsRequest request) { 137 | synchronized (this) { 138 | log.info("cache size=" + cache.size()); 139 | return cache.get(getKey(request)); 140 | } 141 | } 142 | 143 | /** 144 | * Sets the cached image for the request. 145 | * 146 | * @param request 147 | * the WMS http request 148 | * @param image 149 | * bytes of the image 150 | */ 151 | public synchronized void put(WmsRequest request, byte[] image) { 152 | synchronized (this) { 153 | String key = getKey(request); 154 | // make sure it's the last on the list of keys so won't be dropped 155 | // from cache 156 | keys.remove(key); 157 | keys.add(key); 158 | if (keys.size() > maxSize) 159 | remove(keys.get(0)); 160 | if (maxSize > 0 && layers.containsAll(request.getLayers())) { 161 | cache.put(key, image); 162 | log.info("cached image with key=" + key); 163 | } 164 | } 165 | } 166 | 167 | /** 168 | * Flags the given layer as a layer to be cached. 169 | * 170 | * @param layerName 171 | * is the name of the WMS layer 172 | * @return this 173 | */ 174 | public ImageCache add(String layerName) { 175 | setEnabled(layerName, true); 176 | return this; 177 | } 178 | 179 | } -------------------------------------------------------------------------------- /grumpy-ogc/src/main/java/com/github/davidmoten/grumpy/wms/ImageWriter.java: -------------------------------------------------------------------------------- 1 | package com.github.davidmoten.grumpy.wms; 2 | 3 | import java.awt.image.BufferedImage; 4 | import java.io.IOException; 5 | import java.io.OutputStream; 6 | 7 | import javax.imageio.ImageIO; 8 | 9 | /** 10 | * Writes encoded images to an {@link OutputStream}. 11 | */ 12 | public interface ImageWriter { 13 | 14 | /** 15 | * Writes encoded images to an {@link OutputStream}. 16 | * 17 | * @param image 18 | * to be written 19 | * @param os 20 | * {@link OutputStream} to write to 21 | * @param imageType 22 | * type of image as per formatName specification of 23 | * {@link ImageIO#write(java.awt.image.RenderedImage, String, OutputStream)} 24 | * @throws IOException 25 | */ 26 | void writeImage(BufferedImage image, OutputStream os, String imageType) 27 | throws IOException; 28 | } 29 | -------------------------------------------------------------------------------- /grumpy-ogc/src/main/java/com/github/davidmoten/grumpy/wms/ImageWriterDefault.java: -------------------------------------------------------------------------------- 1 | package com.github.davidmoten.grumpy.wms; 2 | 3 | import java.awt.image.BufferedImage; 4 | import java.io.IOException; 5 | import java.io.OutputStream; 6 | 7 | import javax.imageio.ImageIO; 8 | 9 | /** 10 | * Writes images using {@link ImageIO}. This can be a bit slow. Significant 11 | * performance gains have been obtained using PNGEncoder). An implementation 13 | * has not been included with grumpy because the library is not open source. 14 | */ 15 | public class ImageWriterDefault implements ImageWriter { 16 | 17 | @Override 18 | public void writeImage(BufferedImage image, OutputStream os, String imageType) throws IOException { 19 | ImageIO.write(image, imageType, os); 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /grumpy-ogc/src/main/java/com/github/davidmoten/grumpy/wms/Layer.java: -------------------------------------------------------------------------------- 1 | package com.github.davidmoten.grumpy.wms; 2 | 3 | import java.awt.Graphics2D; 4 | import java.awt.Point; 5 | import java.util.Date; 6 | 7 | /** 8 | * Renders or displays info about positions on a WMS layer. 9 | */ 10 | public interface Layer extends HasLayerFeatures { 11 | 12 | /** 13 | * Render some information onto the supplied graphics context 14 | * 15 | * @param g 16 | * - the graphics context used for rendering 17 | * @param bounds 18 | * - the geo-spatial bounding box of the region to be rendered 19 | * @param width 20 | * - of the graphics area in pixels 21 | * @param height 22 | * - of the graphics area in pixels 23 | */ 24 | void render(Graphics2D g, WmsRequest request); 25 | 26 | /** 27 | * Returns info about the given point on the layer formatted as per the 28 | * requested mimeType. 29 | * 30 | * @param time 31 | * @param request 32 | * @param point 33 | * @param mimeType 34 | * @return 35 | */ 36 | String getInfo(Date time, WmsRequest request, Point point, String mimeType); 37 | 38 | } 39 | -------------------------------------------------------------------------------- /grumpy-ogc/src/main/java/com/github/davidmoten/grumpy/wms/LayerFeatures.java: -------------------------------------------------------------------------------- 1 | package com.github.davidmoten.grumpy.wms; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | public class LayerFeatures { 7 | private final List styles; 8 | private final List crs; 9 | private final String name; 10 | private final boolean queryable; 11 | 12 | private LayerFeatures(List styles, List crs, String name, boolean queryable) { 13 | if (name == null) 14 | throw new NullPointerException("name cannot be null"); 15 | this.styles = styles; 16 | this.crs = crs; 17 | this.name = name; 18 | this.queryable = queryable; 19 | } 20 | 21 | public boolean isQueryable() { 22 | return queryable; 23 | } 24 | 25 | public List getStyles() { 26 | return styles; 27 | } 28 | 29 | public List getCrs() { 30 | return crs; 31 | } 32 | 33 | public String getName() { 34 | return name; 35 | } 36 | 37 | public static Builder builder() { 38 | return new Builder(); 39 | } 40 | 41 | public static class Builder { 42 | 43 | private String name; 44 | private List styles = new ArrayList(); 45 | private List crs = new ArrayList(); 46 | private boolean queryable = false; 47 | 48 | private Builder() { 49 | } 50 | 51 | public Builder styles(List styles) { 52 | this.styles = styles; 53 | return this; 54 | } 55 | 56 | public Builder crs(List crs) { 57 | this.crs = crs; 58 | return this; 59 | } 60 | 61 | public Builder style(String style) { 62 | this.styles.add(style); 63 | return this; 64 | } 65 | 66 | public Builder crs(String crs) { 67 | this.crs.add(crs); 68 | return this; 69 | } 70 | 71 | public Builder name(String name) { 72 | this.name = name; 73 | return this; 74 | } 75 | 76 | public Builder queryable(boolean value) { 77 | this.queryable = value; 78 | return this; 79 | } 80 | 81 | public LayerFeatures build() { 82 | return new LayerFeatures(styles, crs, name, queryable); 83 | } 84 | 85 | public Builder queryable() { 86 | return queryable(true); 87 | } 88 | } 89 | 90 | } 91 | -------------------------------------------------------------------------------- /grumpy-ogc/src/main/java/com/github/davidmoten/grumpy/wms/LayerManager.java: -------------------------------------------------------------------------------- 1 | package com.github.davidmoten.grumpy.wms; 2 | 3 | import java.awt.AlphaComposite; 4 | import java.awt.Color; 5 | import java.awt.Graphics2D; 6 | import java.awt.GraphicsEnvironment; 7 | import java.awt.Image; 8 | import java.awt.Point; 9 | import java.awt.image.BufferedImage; 10 | import java.awt.image.ImageObserver; 11 | import java.util.ArrayList; 12 | import java.util.Date; 13 | import java.util.HashMap; 14 | import java.util.List; 15 | import java.util.Map; 16 | import java.util.concurrent.Callable; 17 | import java.util.concurrent.ExecutionException; 18 | import java.util.concurrent.ExecutorService; 19 | import java.util.concurrent.Executors; 20 | import java.util.concurrent.Future; 21 | 22 | import org.slf4j.Logger; 23 | import org.slf4j.LoggerFactory; 24 | 25 | class LayerManager { 26 | 27 | private static Logger log = LoggerFactory.getLogger(LayerManager.class); 28 | 29 | private final Layers layers; 30 | 31 | private final ExecutorService executor; 32 | 33 | private static final boolean DRAW_IN_PARALLEL = true; 34 | 35 | LayerManager(Layers layers) { 36 | this.layers = layers; 37 | GraphicsEnvironment gEnv = GraphicsEnvironment.getLocalGraphicsEnvironment(); 38 | for (String name : gEnv.getAvailableFontFamilyNames()) 39 | log.debug(name); 40 | log.info("constructed"); 41 | executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() + 1); 42 | } 43 | 44 | BufferedImage getImage(WmsRequest request) { 45 | MyGraphics graphics = createGraphics(request); 46 | Graphics2D g = graphics.graphics; 47 | 48 | log.info("painting image with layers"); 49 | // paint the image 50 | paintImage(request, g); 51 | // release resources 52 | g.dispose(); 53 | log.info("image finished"); 54 | return graphics.image; 55 | } 56 | 57 | Map getInfos(Date time, WmsRequest request, Point point, String mimeType) { 58 | Map map = new HashMap(); 59 | for (String layerName : request.getLayers()) { 60 | Layer layer = layers.getLayer(layerName); 61 | if (layer != null) { 62 | String info = layer.getInfo(time, request, point, mimeType); 63 | if (info != null) 64 | map.put(layerName, info); 65 | } else 66 | log.warn("no getInfo implementation for layer: " + layerName); 67 | } 68 | return map; 69 | } 70 | 71 | private static final ImageObserver noActionImageObserver = new ImageObserver() { 72 | @Override 73 | public boolean imageUpdate(Image img, int infoflags, int x, int y, int width, int height) { 74 | return false; 75 | } 76 | }; 77 | 78 | private static class MyGraphics { 79 | public MyGraphics(BufferedImage image, Graphics2D graphics) { 80 | super(); 81 | this.image = image; 82 | this.graphics = graphics; 83 | } 84 | 85 | BufferedImage image; 86 | Graphics2D graphics; 87 | } 88 | 89 | private void prepareGraphics(Graphics2D g) { 90 | RendererUtil.useAntialiasing(g); 91 | } 92 | 93 | private MyGraphics createGraphics(WmsRequest request) { 94 | log.info("creating buffered image"); 95 | BufferedImage image = new BufferedImage(request.getWidth(), request.getHeight(), 96 | BufferedImage.TYPE_INT_ARGB); 97 | Graphics2D g = (Graphics2D) image.getGraphics(); 98 | 99 | // set rendering options 100 | prepareGraphics(g); 101 | 102 | // paint the background with transparency as required 103 | paintBackground(request, g); 104 | 105 | log.info("image ready"); 106 | return new MyGraphics(image, g); 107 | } 108 | 109 | private void paintImage(final WmsRequest request, Graphics2D g) { 110 | 111 | log.info("painting layers " + request.getLayers()); 112 | 113 | if (DRAW_IN_PARALLEL) { 114 | paintImageParallel(request, g); 115 | } else { 116 | // using only a single g2d seems to help with IE8 png transparency 117 | // bug (fixed in IE9). 118 | for (final String layerName : request.getLayers()) { 119 | paintLayer(g, layerName, layers, request); 120 | } 121 | } 122 | } 123 | 124 | private void paintImageParallel(WmsRequest request, Graphics2D g) { 125 | // create future for each worker (layer) 126 | List> futures = new ArrayList>(); 127 | 128 | for (final String layerName : request.getLayers()) { 129 | // create a worker for layer 130 | Callable worker = createWorker(layers, layerName, request); 131 | // start the worker 132 | Future submit = executor.submit(worker); 133 | // record the worker in a list so we can paint the images in 134 | // order later 135 | futures.add(submit); 136 | } 137 | // wait for each image to complete in turn then draw it to the 138 | // everything graphics object 139 | for (Future future : futures) { 140 | drawImage(g, future); 141 | } 142 | } 143 | 144 | private Callable createWorker(final Layers layers, final String layerName, 145 | final WmsRequest request) { 146 | return new Callable() { 147 | @Override 148 | public BufferedImage call() throws Exception { 149 | final MyGraphics graphics = createGraphics(request); 150 | try { 151 | paintLayer(graphics.graphics, layerName, layers, request); 152 | } catch (Throwable t) { 153 | log.warn(t.getMessage(), t); 154 | Graphics2D g = graphics.graphics; 155 | g.setColor(Color.black); 156 | g.setFont(g.getFont().deriveFont(10f)); 157 | g.drawString(t.getClass().getName() + ":" + t.getMessage(), graphics.image.getWidth()/2-50, graphics.image.getHeight() /2 + 10); 158 | } 159 | return graphics.image; 160 | } 161 | }; 162 | } 163 | 164 | private static void paintLayer(Graphics2D g, String layerName, Layers layers, WmsRequest request) { 165 | log.info("painting " + layerName); 166 | final Layer layer = layers.getLayer(layerName); 167 | if (layer != null) { 168 | layer.render(g, request); 169 | log.info("finished painting " + layerName); 170 | } else 171 | log.warn("no paintImage implementation for layer: " + layerName); 172 | } 173 | 174 | private void drawImage(Graphics2D g, Future future) { 175 | try { 176 | BufferedImage image = future.get(); 177 | g.drawImage(image, 0, 0, noActionImageObserver); 178 | } catch (InterruptedException e) { 179 | log.warn(e.getMessage(), e); 180 | } catch (ExecutionException e) { 181 | log.error(e.getMessage(), e); 182 | } 183 | } 184 | 185 | private void paintBackground(WmsRequest request, Graphics2D g) { 186 | g.setColor(request.getBackgroundColor()); 187 | g.setBackground(request.getBackgroundColor()); 188 | if (request.isTransparent()) 189 | g.setComposite(AlphaComposite.getInstance(AlphaComposite.CLEAR)); 190 | else 191 | g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.0f)); 192 | g.fillRect(0, 0, 100, 100); 193 | g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1.0f)); 194 | } 195 | 196 | } 197 | -------------------------------------------------------------------------------- /grumpy-ogc/src/main/java/com/github/davidmoten/grumpy/wms/Layers.java: -------------------------------------------------------------------------------- 1 | package com.github.davidmoten.grumpy.wms; 2 | 3 | public interface Layers { 4 | Layer getLayer(String layerName); 5 | } 6 | -------------------------------------------------------------------------------- /grumpy-ogc/src/main/java/com/github/davidmoten/grumpy/wms/LayersBuilder.java: -------------------------------------------------------------------------------- 1 | package com.github.davidmoten.grumpy.wms; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | public class LayersBuilder { 7 | 8 | public static LayersBuilder builder() { 9 | return new LayersBuilder(); 10 | } 11 | 12 | final Map map = new HashMap(); 13 | 14 | private LayersBuilder() { 15 | // private constructor 16 | } 17 | 18 | public LayersBuilder add(String name, Layer layer) { 19 | map.put(name, layer); 20 | return this; 21 | } 22 | 23 | public Layers build() { 24 | // make defensive copy 25 | final Map m = new HashMap(map); 26 | return new Layers() { 27 | 28 | @Override 29 | public Layer getLayer(String layerName) { 30 | Layer layer = m.get(layerName); 31 | // null return handled by LayerManager with a warning in the log 32 | return layer; 33 | } 34 | 35 | }; 36 | } 37 | } -------------------------------------------------------------------------------- /grumpy-ogc/src/main/java/com/github/davidmoten/grumpy/wms/MissingMandatoryParameterException.java: -------------------------------------------------------------------------------- 1 | package com.github.davidmoten.grumpy.wms; 2 | 3 | public class MissingMandatoryParameterException extends Exception { 4 | 5 | private static final long serialVersionUID = -9206288232141856630L; 6 | 7 | public MissingMandatoryParameterException(String parameter) { 8 | super(parameter + " is a mandatory parameter and was missing"); 9 | } 10 | 11 | } 12 | -------------------------------------------------------------------------------- /grumpy-ogc/src/main/java/com/github/davidmoten/grumpy/wms/RendererUtil.java: -------------------------------------------------------------------------------- 1 | package com.github.davidmoten.grumpy.wms; 2 | 3 | import java.awt.Graphics2D; 4 | import java.awt.RenderingHints; 5 | import java.awt.Shape; 6 | import java.awt.geom.GeneralPath; 7 | import java.awt.geom.Point2D; 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | 11 | import com.github.davidmoten.grumpy.core.Position; 12 | import com.github.davidmoten.grumpy.projection.Projector; 13 | 14 | public class RendererUtil { 15 | 16 | public static void useAntialiasing(Graphics2D g) { 17 | RenderingHints renderHints = new RenderingHints(RenderingHints.KEY_ANTIALIASING, 18 | RenderingHints.VALUE_ANTIALIAS_ON); 19 | renderHints.put(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); 20 | renderHints.put(RenderingHints.KEY_TEXT_ANTIALIASING, 21 | RenderingHints.VALUE_TEXT_ANTIALIAS_ON); 22 | g.addRenderingHints(renderHints); 23 | } 24 | 25 | public static List toPathGreatCircle(Projector projector, List positions) { 26 | return toPath(projector, Position.interpolateLongitude(positions)); 27 | } 28 | 29 | public static List toPath(Projector projector, List positions) { 30 | List list = new ArrayList(); 31 | 32 | if (positions.size() < 2) 33 | throw new RuntimeException("must provide at least two positions"); 34 | 35 | list.add(createPath(projector, positions, 0)); 36 | list.add(createPath(projector, positions, -projector.periodAtLat(0))); 37 | list.add(createPath(projector, positions, projector.periodAtLat(0))); 38 | 39 | return list; 40 | } 41 | 42 | private static List getPathPoints(Projector projector, 43 | List positions, double deltaX) { 44 | List list = new ArrayList(); 45 | Double firstPointLat = null; 46 | Double firstPointLon = null; 47 | org.locationtech.jts.geom.Point firstPoint = null; 48 | for (Position pos : positions) { 49 | Position p = pos.normalizeLongitude(); 50 | if (firstPoint == null) { 51 | firstPoint = projector.getFirstXAfter(projector, p.getLat(), p.getLon(), projector 52 | .getBounds().getMinX() + deltaX); 53 | firstPointLat = p.getLat(); 54 | firstPointLon = p.getLon(); 55 | list.add(firstPoint); 56 | } else { 57 | org.locationtech.jts.geom.Point point = projector 58 | .getGeometryPointInSrsRelativeTo(p.getLat(), p.getLon(), firstPointLat, 59 | firstPointLon, firstPoint.getX(), firstPoint.getY()); 60 | list.add(point); 61 | } 62 | } 63 | return list; 64 | } 65 | 66 | private static GeneralPath createPath(Projector projector, List positions, 67 | double deltaX) { 68 | List points = getPathPoints(projector, positions, deltaX); 69 | GeneralPath path = new GeneralPath(); 70 | boolean first = true; 71 | for (org.locationtech.jts.geom.Point point : points) { 72 | Point2D.Double pt = projector.getTargetPoint(point); 73 | if (first) { 74 | path.moveTo(pt.x, pt.y); 75 | first = false; 76 | } else 77 | path.lineTo(pt.x, pt.y); 78 | } 79 | return path; 80 | } 81 | 82 | public static Point2D[] getPoints(Projector projector, List positions) { 83 | List points = new ArrayList(); 84 | for (Position position : positions) 85 | points.add(projector.toPoint2D(position.getLat(), position.getLon())); 86 | return points.toArray(new Point2D[] {}); 87 | } 88 | 89 | public static List getCircle(Position position, double radiusKm, double numPoints) { 90 | 91 | List positions = new ArrayList(); 92 | for (int i = 0; i < numPoints; i++) { 93 | double bearing = 360.0 * i / numPoints; 94 | Position p = position.predict(radiusKm, bearing).normalizeLongitude(); 95 | positions.add(p); 96 | } 97 | positions.add(positions.get(0)); 98 | return positions; 99 | } 100 | 101 | public static void draw(Graphics2D g, List shapes) { 102 | for (Shape shape : shapes) 103 | g.draw(shape); 104 | } 105 | 106 | public static void fill(Graphics2D g, List shapes) { 107 | for (Shape shape : shapes) 108 | g.fill(shape); 109 | } 110 | 111 | } 112 | -------------------------------------------------------------------------------- /grumpy-ogc/src/main/java/com/github/davidmoten/grumpy/wms/UnknownParameterException.java: -------------------------------------------------------------------------------- 1 | package com.github.davidmoten.grumpy.wms; 2 | 3 | public class UnknownParameterException extends Exception { 4 | 5 | private static final long serialVersionUID = 4788825665080636000L; 6 | 7 | public UnknownParameterException(String message) { 8 | super(message); 9 | } 10 | 11 | } 12 | -------------------------------------------------------------------------------- /grumpy-ogc/src/main/java/com/github/davidmoten/grumpy/wms/WmsRequestProcessor.java: -------------------------------------------------------------------------------- 1 | package com.github.davidmoten.grumpy.wms; 2 | 3 | import java.awt.Point; 4 | import java.awt.image.BufferedImage; 5 | import java.io.BufferedOutputStream; 6 | import java.io.ByteArrayOutputStream; 7 | import java.io.IOException; 8 | import java.io.OutputStream; 9 | import java.text.DecimalFormat; 10 | import java.util.ArrayList; 11 | import java.util.Date; 12 | import java.util.List; 13 | import java.util.Map; 14 | import java.util.Map.Entry; 15 | 16 | import javax.servlet.http.HttpServletRequest; 17 | import javax.servlet.http.HttpServletResponse; 18 | 19 | import org.slf4j.LoggerFactory; 20 | 21 | /** 22 | * Processes a WMS {@link HttpServletRequest} and returns a 23 | * {@link HttpServletResponse}. 24 | */ 25 | public class WmsRequestProcessor { 26 | 27 | private static org.slf4j.Logger log = LoggerFactory.getLogger(WmsRequestProcessor.class); 28 | 29 | private final ImageCache imageCache; 30 | 31 | private final LayerManager layerManager; 32 | 33 | private final ImageWriter imageWriter; 34 | 35 | /** 36 | * Constructor. 37 | * 38 | * @param layers 39 | * @param imageCache 40 | * @param imageWriter 41 | */ 42 | public WmsRequestProcessor(Layers layers, ImageCache imageCache, ImageWriter imageWriter) { 43 | this.imageCache = imageCache; 44 | this.imageWriter = imageWriter; 45 | this.layerManager = new LayerManager(layers); 46 | } 47 | 48 | public static Builder builder() { 49 | return new Builder(); 50 | } 51 | 52 | public static class Builder { 53 | 54 | private ImageCache imageCache = new ImageCache(); 55 | private Layers layers; 56 | private final LayersBuilder layersBuilder = LayersBuilder.builder(); 57 | private final List layersToCache = new ArrayList(); 58 | private ImageWriter imageWriter = new ImageWriterDefault(); 59 | private Integer imageCacheSize; 60 | 61 | private Builder() { 62 | } 63 | 64 | public Builder imageCache(int size) { 65 | this.imageCacheSize = size; 66 | return this; 67 | } 68 | 69 | public Builder addCachedLayer(Layer layer) { 70 | return addCachedLayer(layer.getFeatures().getName(), layer); 71 | } 72 | 73 | public Builder addCachedLayer(String name, Layer layer) { 74 | return addLayer(name, layer, true); 75 | } 76 | 77 | public Builder addLayer(Layer layer) { 78 | return addLayer(layer.getFeatures().getName(), layer); 79 | } 80 | 81 | public Builder addLayer(String name, Layer layer) { 82 | return addLayer(name, layer, false); 83 | } 84 | 85 | public Builder addLayer(String name, Layer layer, boolean cache) { 86 | layersBuilder.add(name, layer); 87 | if (cache) 88 | layersToCache.add(name); 89 | return this; 90 | } 91 | 92 | public Builder layers(Layers layers) { 93 | this.layers = layers; 94 | return this; 95 | } 96 | 97 | public Builder imageWriter(ImageWriter imageWriter) { 98 | this.imageWriter = imageWriter; 99 | return this; 100 | } 101 | 102 | public WmsRequestProcessor build() { 103 | if (imageCacheSize != null) 104 | imageCache = new ImageCache(imageCacheSize); 105 | for (String layer : layersToCache) 106 | imageCache.add(layer); 107 | if (layers == null) 108 | layers = layersBuilder.build(); 109 | return new WmsRequestProcessor(layers, imageCache, imageWriter); 110 | } 111 | } 112 | 113 | public void writeImage(WmsRequest wmsRequest, boolean cacheImage, OutputStream out) 114 | throws IOException { 115 | final byte[] bytes; 116 | if (cacheImage) { 117 | // check the cache for the bytes of the image converted to the 118 | // appropriate format. Note that the critical bottleneck is 119 | // ImageIO.write rather than the layerManager.getImage call 120 | bytes = imageCache.get(wmsRequest); 121 | } else { 122 | bytes = null; 123 | } 124 | final byte[] result; 125 | if (bytes == null) { 126 | log.info("image cache empty"); 127 | 128 | BufferedImage image = null; 129 | // dynamic layers should clear the imageCache in a separate thread 130 | // (for example, using a quartz job) 131 | image = layerManager.getImage(wmsRequest); 132 | // Note that we write the image to memory first to avoid this JRE 133 | // bug: 134 | // http://bugs.sun.com/bugdatabase/view_bug.do;jsessionid=dc84943191e06dffffffffdf200f5210dd319?bug_id=6967419 135 | // which is commented on further in JIRA ER-95 136 | log.info("writing image to memory for layers " + wmsRequest.getLayers()); 137 | ByteArrayOutputStream byteOs = new ByteArrayOutputStream(); 138 | String imageType = wmsRequest.getFormat() 139 | .substring(wmsRequest.getFormat().indexOf('/') + 1); 140 | // This call is slow!! 141 | long t = System.currentTimeMillis(); 142 | imageWriter.writeImage(image, byteOs, imageType); 143 | log.info("ImageIoWriteTimeMs=" + (System.currentTimeMillis() - t)); 144 | result = byteOs.toByteArray(); 145 | imageCache.put(wmsRequest, result); 146 | } else { 147 | result = bytes; 148 | log.info("obtained image from cache for layers " + wmsRequest.getLayers()); 149 | } 150 | 151 | log.info("writing image to http output stream for layers " + wmsRequest.getLayers()); 152 | out.write(result); 153 | out.flush(); 154 | log.info("imageSizeK=" + new DecimalFormat("0.000").format(result.length / 1000.0) 155 | + " for layers " + wmsRequest.getLayers()); 156 | } 157 | 158 | public void writeFeatureInfo(int i, int j, WmsRequest wmsRequest, OutputStream out) 159 | throws IOException { 160 | BufferedOutputStream bos = new BufferedOutputStream(out); 161 | Map infos = layerManager.getInfos(new Date(), wmsRequest, new Point(i, j), 162 | wmsRequest.getInfoFormat()); 163 | for (Entry entry : infos.entrySet()) { 164 | log.debug(entry.getKey() + "=" + entry.getValue()); 165 | bos.write(("

" + entry.getKey() + "

").getBytes()); 166 | bos.write(entry.getValue().getBytes()); 167 | } 168 | bos.flush(); 169 | } 170 | 171 | } 172 | -------------------------------------------------------------------------------- /grumpy-ogc/src/main/java/com/github/davidmoten/grumpy/wms/WmsServletRequestProcessor.java: -------------------------------------------------------------------------------- 1 | package com.github.davidmoten.grumpy.wms; 2 | 3 | import java.io.IOException; 4 | import java.io.OutputStream; 5 | import java.text.DecimalFormat; 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | 9 | import javax.servlet.ServletException; 10 | import javax.servlet.http.HttpServletRequest; 11 | import javax.servlet.http.HttpServletResponse; 12 | 13 | import org.slf4j.LoggerFactory; 14 | 15 | /** 16 | * Processes a WMS {@link HttpServletRequest} and returns a 17 | * {@link HttpServletResponse}. 18 | */ 19 | public class WmsServletRequestProcessor { 20 | 21 | private static org.slf4j.Logger log = LoggerFactory.getLogger(WmsServletRequestProcessor.class); 22 | 23 | private static final String REQUEST_GET_MAP = "GetMap"; 24 | private static final String PARAMETER_REQUEST = "REQUEST"; 25 | private static final Object REQUEST_GET_CAPABILITIES = "GetCapabilities"; 26 | private static final Object REQUEST_GET_FEATURE_INFO = "GetFeatureInfo"; 27 | 28 | private final CapabilitiesProvider capabilitiesProvider; 29 | 30 | private WmsRequestProcessor processor; 31 | 32 | /** 33 | * Constructor. 34 | * 35 | * @param capabilitiesProvider 36 | * @param layers 37 | * @param imageCache 38 | * @param imageWriter 39 | */ 40 | public WmsServletRequestProcessor(CapabilitiesProvider capabilitiesProvider, 41 | WmsRequestProcessor processor) { 42 | this.capabilitiesProvider = capabilitiesProvider; 43 | this.processor = processor; 44 | } 45 | 46 | public static Builder builder() { 47 | return new Builder(); 48 | } 49 | 50 | public static class Builder { 51 | 52 | private CapabilitiesProvider capabilitiesProvider = new CapabilitiesProviderEmpty(); 53 | private ImageCache imageCache = new ImageCache(); 54 | private Layers layers; 55 | private final LayersBuilder layersBuilder = LayersBuilder.builder(); 56 | private final List layersToCache = new ArrayList(); 57 | private ImageWriter imageWriter = new ImageWriterDefault(); 58 | private Integer imageCacheSize; 59 | 60 | private Builder() { 61 | } 62 | 63 | public Builder capabilities(CapabilitiesProvider capabilitiesProvider) { 64 | this.capabilitiesProvider = capabilitiesProvider; 65 | return this; 66 | } 67 | 68 | public Builder capabilities(Capabilities capabilities) { 69 | this.capabilitiesProvider = new CapabilitiesProviderFromCapabilities(capabilities); 70 | return this; 71 | } 72 | 73 | public Builder capabilitiesFromClasspath(String resource) { 74 | this.capabilitiesProvider = CapabilitiesProviderFromClasspath.fromClasspath(resource); 75 | return this; 76 | } 77 | 78 | public Builder imageCache(int size) { 79 | this.imageCacheSize = size; 80 | return this; 81 | } 82 | 83 | public Builder addCachedLayer(Layer layer) { 84 | return addCachedLayer(layer.getFeatures().getName(), layer); 85 | } 86 | 87 | /** 88 | * Adds the layer with cacheable images when generated and uses the given name 89 | * for the layer for WMS calls (this will override the layer name defined in 90 | * {#link {@link Layer#getFeatures()}. 91 | * 92 | * @param name override name for layer 93 | * @param layer layer to add with cacheable images 94 | * @return this 95 | */ 96 | public Builder addCachedLayer(String name, Layer layer) { 97 | return addLayer(name, layer, true); 98 | } 99 | 100 | public Builder addLayer(Layer layer) { 101 | return addLayer(layer.getFeatures().getName(), layer); 102 | } 103 | 104 | /** 105 | * Adds the layer where generated images are non-cacheable and uses the given 106 | * name for the layer for WMS calls (this will override the layer name defined 107 | * in {#link {@link Layer#getFeatures()}. 108 | * 109 | * @param name override name for layer 110 | * @param layer layer to add with cacheable images 111 | * @return this 112 | */ 113 | public Builder addLayer(String name, Layer layer) { 114 | return addLayer(name, layer, false); 115 | } 116 | 117 | /** 118 | * Adds the layer where generated images are cacheable according to parameter 119 | * {@code cache} and uses the given name for the layer for WMS calls (this will 120 | * override the layer name defined in {#link {@link Layer#getFeatures()}. 121 | * 122 | * @param name override name for layer 123 | * @param layer layer to add with cacheable images 124 | * @param cache if and only if true images will be cached up to the max image 125 | * cache size 126 | * @return this 127 | */ 128 | public Builder addLayer(String name, Layer layer, boolean cache) { 129 | layersBuilder.add(name, layer); 130 | if (cache) 131 | layersToCache.add(name); 132 | return this; 133 | } 134 | 135 | public Builder layers(Layers layers) { 136 | this.layers = layers; 137 | return this; 138 | } 139 | 140 | public Builder imageWriter(ImageWriter imageWriter) { 141 | this.imageWriter = imageWriter; 142 | return this; 143 | } 144 | 145 | public WmsServletRequestProcessor build() { 146 | if (imageCacheSize != null) 147 | imageCache = new ImageCache(imageCacheSize); 148 | for (String layer : layersToCache) 149 | imageCache.add(layer); 150 | if (layers == null) 151 | layers = layersBuilder.build(); 152 | WmsRequestProcessor processor = new WmsRequestProcessor(layers, imageCache, 153 | imageWriter); 154 | return new WmsServletRequestProcessor(capabilitiesProvider, processor); 155 | } 156 | } 157 | 158 | public void doGet(HttpServletRequest request, HttpServletResponse response) 159 | throws ServletException, IOException { 160 | long t = System.currentTimeMillis(); 161 | try { 162 | log.info("httpGetUrl=" + request.getRequestURL() + "?" + request.getQueryString()); 163 | log.info("requestedByIP = ip " + request.getRemoteAddr()); 164 | String req = request.getParameter(PARAMETER_REQUEST); 165 | setNoCacheParameters(response); 166 | if (REQUEST_GET_CAPABILITIES.equals(req)) { 167 | writeCapabilities(request, response); 168 | } else if (REQUEST_GET_MAP.equals(req)) { 169 | writeImage(request, response); 170 | } else if (REQUEST_GET_FEATURE_INFO.equals(req)) { 171 | writeFeatureInfo(request, response); 172 | } else 173 | throw new UnknownParameterException("Unrecognized REQUEST parameter: " + req); 174 | // flush everything so timer below is realistic for delivery to 175 | // client 176 | response.getOutputStream().flush(); 177 | } catch (UnknownParameterException e) { 178 | log.warn(e.getMessage()); 179 | throw new ServletException(e); 180 | } catch (MissingMandatoryParameterException e) { 181 | log.warn(e.getMessage(), e); 182 | throw new ServletException(e); 183 | } catch (Exception e) { 184 | handleException(e); 185 | } finally { 186 | log.info("requestTimeSeconds=" 187 | + new DecimalFormat("0.000").format((System.currentTimeMillis() - t) / 1000.0) 188 | + "s"); 189 | } 190 | } 191 | 192 | private void handleException(Exception e) throws ServletException { 193 | if (e.getClass().getName().contains("ClientAbortException") 194 | || e.getMessage() != null && e.getMessage().contains("Broken pipe") 195 | || e.getCause() instanceof java.net.SocketException) { 196 | String s = e.getMessage(); 197 | if (s == null) 198 | s = e.getClass().getName(); 199 | log.warn(e.getMessage()); 200 | } else { 201 | log.error(e.getClass().getName()); 202 | log.error(e.getMessage(), e); 203 | throw new ServletException(e); 204 | } 205 | } 206 | 207 | private void writeCapabilities(HttpServletRequest request, HttpServletResponse response) 208 | throws IOException { 209 | response.setContentType("text/xml"); 210 | String capabilities = capabilitiesProvider.getCapabilities(request); 211 | response.getOutputStream().write(capabilities.getBytes()); 212 | 213 | } 214 | 215 | private void writeImage(HttpServletRequest request, HttpServletResponse response) 216 | throws MissingMandatoryParameterException, IOException { 217 | log.info("getting image"); 218 | WmsRequest wmsRequest = new WmsRequest(request); 219 | OutputStream out = response.getOutputStream(); 220 | response.setContentType(wmsRequest.getFormat()); 221 | boolean cacheImage = "true".equalsIgnoreCase(request.getParameter("cacheImage")); 222 | 223 | processor.writeImage(wmsRequest, cacheImage, out); 224 | } 225 | 226 | private void writeFeatureInfo(HttpServletRequest request, HttpServletResponse response) 227 | throws MissingMandatoryParameterException, IOException { 228 | log.info("getting feature info"); 229 | int i = getI(request); 230 | int j = getJ(request); 231 | WmsRequest wmsRequest = new WmsRequest(request); 232 | response.setContentType(wmsRequest.getInfoFormat()); 233 | processor.writeFeatureInfo(i, j, wmsRequest, response.getOutputStream()); 234 | } 235 | 236 | private int getJ(HttpServletRequest request) { 237 | if (request.getParameter("J") != null) 238 | return Math.round(Float.parseFloat(request.getParameter("J"))); 239 | else 240 | // GAIA uses x, y instead of spec I,J! 241 | return Math.round(Float.parseFloat(request.getParameter("Y"))); 242 | 243 | } 244 | 245 | private int getI(HttpServletRequest request) { 246 | if (request.getParameter("J") != null) 247 | return Math.round(Float.parseFloat(request.getParameter("I"))); 248 | else 249 | // GAIA uses x, y instead of spec I,J! 250 | return Math.round(Float.parseFloat(request.getParameter("X"))); 251 | 252 | } 253 | 254 | private void setNoCacheParameters(HttpServletResponse response) { 255 | // Set to expire far in the past. 256 | response.setHeader("Expires", "-1"); 257 | 258 | // Set standard HTTP/1.1 no-cache headers. 259 | response.setHeader("Cache-Control", "no-store, no-cache, must-revalidate"); 260 | 261 | // Set IE extended HTTP/1.1 no-cache headers (use addHeader). 262 | response.addHeader("Cache-Control", "post-check=0, pre-check=0"); 263 | 264 | // Set standard HTTP/1.0 no-cache header. 265 | response.setHeader("Pragma", "no-cache"); 266 | } 267 | 268 | } 269 | -------------------------------------------------------------------------------- /grumpy-ogc/src/main/java/com/github/davidmoten/grumpy/wms/WmsUtil.java: -------------------------------------------------------------------------------- 1 | package com.github.davidmoten.grumpy.wms; 2 | 3 | import java.awt.Color; 4 | import java.awt.Rectangle; 5 | import java.lang.reflect.Field; 6 | import java.util.ArrayList; 7 | import java.util.Collections; 8 | import java.util.List; 9 | 10 | import org.geotools.geometry.jts.ReferencedEnvelope; 11 | import org.geotools.renderer.lite.RendererUtilities; 12 | 13 | import com.github.davidmoten.grumpy.core.Position; 14 | import com.github.davidmoten.grumpy.projection.FeatureUtil; 15 | import com.github.davidmoten.grumpy.projection.Projector; 16 | import com.github.davidmoten.grumpy.projection.ProjectorBounds; 17 | import com.github.davidmoten.grumpy.projection.ProjectorTarget; 18 | 19 | public class WmsUtil { 20 | 21 | public static List getColorFromStyles(List styles) { 22 | List colors = new ArrayList(); 23 | for (String style : styles) { 24 | Field field; 25 | try { 26 | field = Color.class.getField(style); 27 | Color color = (Color) field.get(null); 28 | colors.add(color); 29 | } catch (SecurityException e) { 30 | // ignore 31 | } catch (NoSuchFieldException e) { 32 | // ignore 33 | } catch (IllegalArgumentException e) { 34 | // ignore 35 | } catch (IllegalAccessException e) { 36 | // ignore; 37 | } 38 | } 39 | return colors; 40 | } 41 | 42 | public static Projector getProjector(WmsRequest request) { 43 | ProjectorTarget target = new ProjectorTarget(request.getWidth(), request.getHeight()); 44 | return new Projector(request.getBounds(), target); 45 | } 46 | 47 | public static double getScale(WmsRequest request) { 48 | ProjectorBounds b = request.getBounds(); 49 | ReferencedEnvelope envelope = new ReferencedEnvelope(b.getMinX(), b.getMaxX(), b.getMinY(), 50 | b.getMaxY(), FeatureUtil.getCrs(request.getCrs())); 51 | return RendererUtilities.calculateOGCScale(envelope, request.getWidth(), 52 | Collections.emptyMap()); 53 | } 54 | 55 | public static Rectangle toTargetRectangle(Projector projector) { 56 | ProjectorTarget t = projector.getTarget(); 57 | return new Rectangle(0, 0, t.getWidth(), t.getHeight()); 58 | } 59 | 60 | public static List getBorder(Projector projector, Rectangle region) { 61 | List box = new ArrayList(); 62 | box.add(projector.toPosition(region.x, region.y)); 63 | box.add(projector.toPosition(region.x, region.y + region.height)); 64 | box.add(projector.toPosition(region.x + region.width, region.y + region.height)); 65 | box.add(projector.toPosition(region.x + region.width, region.y)); 66 | box.add(projector.toPosition(region.x, region.y)); 67 | return box; 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /grumpy-ogc/src/main/java/com/github/davidmoten/grumpy/wms/reduction/RectangleSampler.java: -------------------------------------------------------------------------------- 1 | package com.github.davidmoten.grumpy.wms.reduction; 2 | 3 | import java.awt.Point; 4 | import java.awt.Rectangle; 5 | import java.util.List; 6 | 7 | import com.github.davidmoten.grumpy.projection.Projector; 8 | 9 | public interface RectangleSampler { 10 | 11 | List sample(Rectangle region, Projector projector); 12 | 13 | } 14 | -------------------------------------------------------------------------------- /grumpy-ogc/src/main/java/com/github/davidmoten/grumpy/wms/reduction/RectangleSamplerCorners.java: -------------------------------------------------------------------------------- 1 | package com.github.davidmoten.grumpy.wms.reduction; 2 | 3 | import java.awt.Point; 4 | import java.awt.Rectangle; 5 | import java.util.List; 6 | 7 | import com.github.davidmoten.grumpy.projection.Projector; 8 | 9 | public class RectangleSamplerCorners implements RectangleSampler { 10 | 11 | @Override 12 | public List sample(Rectangle region, Projector projector) { 13 | return RectangleUtil.corners(region); 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /grumpy-ogc/src/main/java/com/github/davidmoten/grumpy/wms/reduction/RectangleSamplerGrid.java: -------------------------------------------------------------------------------- 1 | package com.github.davidmoten.grumpy.wms.reduction; 2 | 3 | import java.awt.Point; 4 | import java.awt.Rectangle; 5 | import java.util.List; 6 | 7 | import com.github.davidmoten.grumpy.projection.Projector; 8 | 9 | /** 10 | * Samples bounds by creating a grid starting at top left corner of 11 | * min(maxSizeKm,widthKm) by min(maxSizeKm, heightKm). Always includes bottom 12 | * and right edge points as well. 13 | */ 14 | public class RectangleSamplerGrid implements RectangleSampler { 15 | 16 | @Override 17 | public List sample(Rectangle region, Projector projector) { 18 | return RectangleUtil.corners(region); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /grumpy-ogc/src/main/java/com/github/davidmoten/grumpy/wms/reduction/RectangleUtil.java: -------------------------------------------------------------------------------- 1 | package com.github.davidmoten.grumpy.wms.reduction; 2 | 3 | import java.awt.Point; 4 | import java.awt.Rectangle; 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | 8 | public class RectangleUtil { 9 | 10 | public static List corners(Rectangle region) { 11 | List list = new ArrayList(); 12 | list.add(new Point(region.x, region.y)); 13 | list.add(new Point(region.x, region.y + region.height)); 14 | list.add(new Point(region.x + region.width, region.y)); 15 | list.add(new Point(region.x + region.width, region.y + region.height)); 16 | return list; 17 | } 18 | 19 | public static List splitHorizontally(Rectangle region) { 20 | List list = new ArrayList(); 21 | int halfWidth = region.width / 2; 22 | list.add(new Rectangle(region.x, region.y, halfWidth, region.height)); 23 | list.add(new Rectangle(region.x + halfWidth, region.y, region.width - halfWidth, 24 | region.height)); 25 | return list; 26 | } 27 | 28 | public static List splitVertically(Rectangle region) { 29 | List list = new ArrayList(); 30 | int halfHeight = region.height / 2; 31 | list.add(new Rectangle(region.x, region.y, region.width, halfHeight)); 32 | list.add(new Rectangle(region.x, region.y + halfHeight, region.width, region.height 33 | - halfHeight)); 34 | return list; 35 | } 36 | 37 | public static List quarter(Rectangle region) { 38 | List list = new ArrayList(); 39 | for (Rectangle r : splitHorizontally(region)) 40 | list.addAll(splitVertically(r)); 41 | return list; 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /grumpy-ogc/src/main/java/com/github/davidmoten/grumpy/wms/reduction/Reducer.java: -------------------------------------------------------------------------------- 1 | package com.github.davidmoten.grumpy.wms.reduction; 2 | 3 | import static com.github.davidmoten.grumpy.wms.reduction.RectangleUtil.quarter; 4 | import static com.github.davidmoten.grumpy.wms.reduction.RectangleUtil.splitHorizontally; 5 | import static com.github.davidmoten.grumpy.wms.reduction.RectangleUtil.splitVertically; 6 | 7 | import java.awt.Graphics2D; 8 | import java.awt.Point; 9 | import java.awt.Rectangle; 10 | import java.util.List; 11 | 12 | import com.github.davidmoten.grumpy.core.Position; 13 | import com.github.davidmoten.grumpy.function.Function; 14 | import com.github.davidmoten.grumpy.projection.Projector; 15 | import com.github.davidmoten.grumpy.wms.WmsUtil; 16 | 17 | public class Reducer { 18 | 19 | public static void render(Graphics2D g, Function function, 20 | Projector projector, RectangleSampler sampler, ValueRenderer regionRenderer) { 21 | Rectangle region = WmsUtil.toTargetRectangle(projector); 22 | renderRegion(g, function, projector, region, sampler, regionRenderer); 23 | } 24 | 25 | private static void renderRegion(Graphics2D g, Function function, 26 | Projector projector, Rectangle region, RectangleSampler sampler, 27 | ValueRenderer regionRenderer) { 28 | 29 | // check if we need to divide the region 30 | boolean regionDivisible = region.height > 1 || region.width > 1; 31 | 32 | final T regionUniformValue; 33 | if (!regionDivisible) { 34 | // region is indivisible, so choose any corner for the 35 | // representative value 36 | regionUniformValue = function.apply(projector.toPosition(region.getMinX(), 37 | region.getMinY())); 38 | } else { 39 | // get the function value for the region if common to all sample 40 | // points in the region (if no common value returns null) 41 | regionUniformValue = getUniformSampledValue(projector, region, sampler, function); 42 | } 43 | 44 | if (regionUniformValue != null) { 45 | // render the region 46 | regionRenderer.render(g, projector, region, regionUniformValue); 47 | } else { 48 | // region is a mix of values and is divisible 49 | // so divide into sub regions ... 2 or 4 50 | // but only if we can 51 | 52 | splitRegionAndRender(g, function, projector, region, sampler, regionRenderer); 53 | } 54 | } 55 | 56 | private static void splitRegionAndRender(Graphics2D g, Function function, 57 | Projector projector, Rectangle region, RectangleSampler sampler, 58 | ValueRenderer regionRenderer) { 59 | // split region 60 | final List regions = splitRegion(region); 61 | 62 | // now render each region 63 | for (Rectangle subRegion : regions) { 64 | renderRegion(g, function, projector, subRegion, sampler, regionRenderer); 65 | } 66 | } 67 | 68 | private static List splitRegion(Rectangle region) { 69 | if (region.width > 1 && region.height > 1) 70 | return quarter(region); 71 | else if (region.height > 1) 72 | return splitVertically(region); 73 | else 74 | return splitHorizontally(region); 75 | } 76 | 77 | private static T getUniformSampledValue(Projector projector, Rectangle region, 78 | RectangleSampler sampler, Function function) { 79 | 80 | T firstT = null; 81 | 82 | List points = sampler.sample(region, projector); 83 | 84 | for (Point point : points) { 85 | Position p = projector.toPosition(point.x, point.y); 86 | T t = function.apply(p); 87 | if (firstT == null) { 88 | firstT = t; 89 | } else if (!firstT.equals(t)) { 90 | return null; 91 | } 92 | } 93 | return firstT; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /grumpy-ogc/src/main/java/com/github/davidmoten/grumpy/wms/reduction/ValueRenderer.java: -------------------------------------------------------------------------------- 1 | package com.github.davidmoten.grumpy.wms.reduction; 2 | 3 | import java.awt.Graphics2D; 4 | import java.awt.Rectangle; 5 | 6 | import com.github.davidmoten.grumpy.projection.Projector; 7 | 8 | public interface ValueRenderer { 9 | void render(Graphics2D g, Projector projector, Rectangle region, final T t); 10 | } 11 | -------------------------------------------------------------------------------- /grumpy-ogc/src/main/java/com/github/davidmoten/util/servlet/RequestUtil.java: -------------------------------------------------------------------------------- 1 | package com.github.davidmoten.util.servlet; 2 | 3 | import java.util.Arrays; 4 | import java.util.List; 5 | 6 | import javax.servlet.http.HttpServletRequest; 7 | 8 | import com.github.davidmoten.grumpy.wms.MissingMandatoryParameterException; 9 | 10 | public class RequestUtil { 11 | 12 | private static final String COMMA = ","; 13 | 14 | public static List getList(HttpServletRequest request, String parameter, 15 | boolean mandatory) { 16 | String[] items = new String[] {}; 17 | if (request.getParameter(parameter) != null) 18 | items = request.getParameter(parameter).split(COMMA); 19 | return Arrays.asList(items); 20 | } 21 | 22 | public static String getParameter(HttpServletRequest request, String parameter, 23 | boolean mandatory) throws MissingMandatoryParameterException { 24 | 25 | String s = request.getParameter(parameter); 26 | if (s == null && mandatory) 27 | throw new MissingMandatoryParameterException(parameter); 28 | return s; 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /grumpy-ogc/src/main/resources/wms-capabilities-empty.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /grumpy-ogc/src/main/resources/wms-capabilities-template.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 10 | ${serviceName} 11 | 12 | ${serviceTitle} 13 | 14 | 15 | ${serviceAbstract} 16 | 18 | 22 | 23 | 24 | 25 | 26 | 27 | none 28 | none 29 | 20 30 | ${serviceMaxWidth} 31 | ${serviceMaxHeight} 32 | 33 | 34 | 35 | 36 | 37 | text/xml 38 | 39 | 40 | 41 | 44 | 45 | 46 | 47 | 48 | 49 | ${imageFormats} 50 | 51 | 52 | 53 | 56 | 57 | 58 | 59 | 60 | 61 | ${infoFormats} 62 | 63 | 64 | 65 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | XML 75 | INIMAGE 76 | BLANK 77 | 78 | 79 | ${layers} 80 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /grumpy-ogc/src/test/java/com/github/davidmoten/grumpy/wms/CapabilitiesProviderFromCapabilitiesTest.java: -------------------------------------------------------------------------------- 1 | package com.github.davidmoten.grumpy.wms; 2 | 3 | import org.junit.Test; 4 | 5 | public class CapabilitiesProviderFromCapabilitiesTest { 6 | 7 | @Test 8 | public void test() { 9 | Capabilities cap = Capabilities // 10 | .builder() // 11 | .serviceName("CustomOgc") // 12 | .serviceTitle("Custom OGC") // 13 | .serviceAbstract( 14 | "Custom OGC WMS services including Custom, Fiddle and Darkness layers") // 15 | .serviceBaseUrl("https://base/wms") // 16 | .serviceMaxHeight(2000) // 17 | .serviceMaxWidth(2000) // 18 | .imageFormat("image/png") // 19 | .imageFormat("image/jpeg") // 20 | .infoFormat("text/html") // 21 | .layer(CapabilitiesLayer.builder().name("Custom").title("Custom WMS Layer") 22 | .queryable(true).opaque(true).style("plain").crs("EPSG:4326") 23 | .crs("EPSG:3857").build()).build(); 24 | CapabilitiesProviderFromCapabilities p = new CapabilitiesProviderFromCapabilities(cap); 25 | System.out.println(p.getCapabilities(null)); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /grumpy-ogc/src/test/java/com/github/davidmoten/grumpy/wms/RendererUtilTest.java: -------------------------------------------------------------------------------- 1 | package com.github.davidmoten.grumpy.wms; 2 | 3 | import com.github.davidmoten.grumpy.projection.Projector; 4 | import com.github.davidmoten.grumpy.projection.ProjectorBounds; 5 | import com.github.davidmoten.grumpy.projection.ProjectorTarget; 6 | import org.junit.Test; 7 | import org.locationtech.jts.geom.Point; 8 | 9 | import java.awt.geom.GeneralPath; 10 | import java.awt.geom.PathIterator; 11 | import java.awt.geom.Point2D.Double; 12 | import java.util.List; 13 | 14 | import static com.github.davidmoten.grumpy.core.Position.position; 15 | 16 | public class RendererUtilTest { 17 | 18 | @Test 19 | public void testTransformWrapping() { 20 | ProjectorTarget target = new ProjectorTarget(300, 200); 21 | ProjectorBounds bounds = new ProjectorBounds("EPSG:3857", 18924313.4349, -4865942.2795, -18924313.4349, 22 | -3503549.8435); 23 | Projector projector = new Projector(bounds, target); 24 | Point p = projector.getGeometryPointInSrs(-35, 140); 25 | Double point = projector.getTargetPoint(p); 26 | System.out.println("x1=" + p.getX() + " point=" + point); 27 | Point p2 = projector.getGeometryPointInSrsRelativeTo(-35, 141, -35, 140, p.getX(), p.getY()); 28 | System.out.println("x2=" + p2.getX()); 29 | double x3 = p.getX() - projector.periodAtLat(-35); 30 | Double point3 = projector.getTargetPoint(projector.createPoint(x3, p.getY())); 31 | System.out.println("x3=" + x3 + " point=" + point3); 32 | double x4 = p.getX() + projector.periodAtLat(-35); 33 | Double point4 = projector.getTargetPoint(projector.createPoint(x4, p.getY())); 34 | System.out.println("x4=" + x4 + " point=" + point4); 35 | 36 | } 37 | 38 | @Test 39 | public void testGetCircle() { 40 | ProjectorTarget target = new ProjectorTarget(300, 200); 41 | // 14288114.828624,-6061227.593083,18907357.32131,-2348222.5076192 42 | ProjectorBounds bounds = new ProjectorBounds("EPSG:3857", 14288114.828624, -6061227.593083, 18907357.32131, 43 | -2348222.5076192); 44 | Projector projector = new Projector(bounds, target); 45 | List paths = RendererUtil.toPath(projector, 46 | RendererUtil.getCircle(position(-35.3075, 149.1244), 400, 36)); 47 | for (GeneralPath path : paths) { 48 | System.out.println("Path"); 49 | PathIterator it = path.getPathIterator(null); 50 | double[] values = new double[6]; 51 | while (!it.isDone()) { 52 | int code = it.currentSegment(values); 53 | if (code == PathIterator.SEG_MOVETO) { 54 | System.out.println("moveto " + values[0] + "," + values[1]); 55 | } else if (code == PathIterator.SEG_LINETO) { 56 | System.out.println("lineto " + values[0] + "," + values[1]); 57 | } else { 58 | System.out.println("code=" + code); 59 | } 60 | it.next(); 61 | } 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /grumpy-ogc/src/test/java/com/github/davidmoten/grumpy/wms/reduction/BoundsSamplerMaxSizeTest.java: -------------------------------------------------------------------------------- 1 | package com.github.davidmoten.grumpy.wms.reduction; 2 | 3 | import java.awt.Rectangle; 4 | 5 | import org.junit.Test; 6 | 7 | public class BoundsSamplerMaxSizeTest { 8 | 9 | @Test 10 | public void testSample() { 11 | RectangleSampler b = new RectangleSamplerGrid(); 12 | Rectangle r = new Rectangle(0, 0, 200, 300); 13 | System.out.println(b.sample(r, null)); 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /grumpy-ogc/src/test/resources/log4j.properties: -------------------------------------------------------------------------------- 1 | log4j.rootLogger= INFO, console 2 | log4j.appender.console=org.apache.log4j.ConsoleAppender 3 | log4j.appender.console.layout=org.apache.log4j.PatternLayout 4 | 5 | # Print the date in ISO 8601 format 6 | #log4j.appender.console.layout.ConversionPattern=%d [%t] %-5p %c - %m%n 7 | log4j.appender.console.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss.SSS} %-5p %c - %m%n 8 | 9 | # Print only messages of level WARN or above in the package com.foo. 10 | #log4j.logger.com.foo=WARN -------------------------------------------------------------------------------- /grumpy-projection/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 4.0.0 5 | 6 | 7 | com.github.davidmoten 8 | grumpy 9 | 0.4.9-SNAPSHOT 10 | 11 | 12 | grumpy-projection 13 | 14 | ${project.artifactId} 15 | OGC tools including WMS server 16 | jar 17 | 18 | http://github.com/davidmoten/grumpy 19 | 20 | 21 | 22 | 23 | com.github.davidmoten 24 | grumpy-core 25 | ${project.parent.version} 26 | 27 | 28 | 29 | commons-io 30 | commons-io 31 | 2.19.0 32 | 33 | 34 | 35 | org.slf4j 36 | slf4j-api 37 | ${slf4j.version} 38 | 39 | 40 | 41 | org.geotools 42 | gt-main 43 | ${geotools.version} 44 | false 45 | 46 | 47 | org.geotools 48 | gt-render 49 | ${geotools.version} 50 | false 51 | 52 | 53 | org.geotools 54 | gt-epsg-wkt 55 | ${geotools.version} 56 | false 57 | 58 | 59 | 60 | 61 | 62 | junit 63 | junit 64 | ${junit.version} 65 | test 66 | 67 | 68 | log4j 69 | log4j 70 | 1.2.17.norce 71 | test 72 | 73 | 74 | org.slf4j 75 | slf4j-log4j12 76 | ${slf4j.version} 77 | test 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | maven-compiler-plugin 86 | 3.14.0 87 | 88 | ${maven.compiler.target} 89 | ${maven.compiler.target} 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | osgeo 98 | Open Source Geospatial Foundation Repository 99 | https://repo.osgeo.org/repository/release 100 | 101 | 102 | 103 | 104 | -------------------------------------------------------------------------------- /grumpy-projection/src/main/java/com/github/davidmoten/grumpy/projection/FeatureUtil.java: -------------------------------------------------------------------------------- 1 | package com.github.davidmoten.grumpy.projection; 2 | 3 | import com.github.davidmoten.grumpy.core.Position; 4 | import org.apache.commons.io.IOUtils; 5 | import org.geotools.geometry.jts.JTS; 6 | import org.geotools.referencing.CRS; 7 | import org.locationtech.jts.geom.Coordinate; 8 | import org.locationtech.jts.geom.GeometryFactory; 9 | import org.locationtech.jts.geom.Point; 10 | import org.opengis.geometry.MismatchedDimensionException; 11 | import org.opengis.referencing.FactoryException; 12 | import org.opengis.referencing.NoSuchAuthorityCodeException; 13 | import org.opengis.referencing.crs.CoordinateReferenceSystem; 14 | import org.opengis.referencing.operation.MathTransform; 15 | import org.opengis.referencing.operation.TransformException; 16 | 17 | import java.io.IOException; 18 | import java.util.Map; 19 | import java.util.concurrent.ConcurrentHashMap; 20 | 21 | public class FeatureUtil { 22 | public static final String EPSG_4326 = "EPSG:4326"; 23 | public static final String EPSG_900913 = "EPSG:900913"; 24 | public static final String EPSG_3857 = "EPSG:3857"; 25 | /** 26 | * ARCGIS copy of 3857 27 | */ 28 | public static final String EPSG_102100 = "EPSG:102100"; 29 | 30 | private static Map crs = new ConcurrentHashMap(); 31 | 32 | public static synchronized CoordinateReferenceSystem getCrs(String epsg) { 33 | try { 34 | if (crs.get(epsg) != null) 35 | return crs.get(epsg); 36 | 37 | if (epsg.equals(EPSG_900913)) { 38 | String wkt = IOUtils.toString(FeatureUtil.class 39 | .getResourceAsStream("/epsg/EPSG_900913.txt")); 40 | crs.put(epsg, CRS.parseWKT(wkt)); 41 | } else if (epsg.equals(EPSG_102100)) { 42 | String wkt = IOUtils.toString(FeatureUtil.class 43 | .getResourceAsStream("/epsg/EPSG_102100.txt")); 44 | crs.put(epsg, CRS.parseWKT(wkt)); 45 | } else 46 | crs.put(epsg, CRS.decode(epsg)); 47 | return crs.get(epsg); 48 | } catch (FactoryException e) { 49 | throw new RuntimeException("could not load " + epsg, e); 50 | } catch (IOException e) { 51 | throw new RuntimeException(e); 52 | } 53 | } 54 | 55 | public static Point createPoint(double lat, double lon, String srsName) { 56 | GeometryFactory geometryFactory = new GeometryFactory(); 57 | Coordinate coordinate = new Coordinate(lon, lat); 58 | Point point = geometryFactory.createPoint(coordinate); 59 | 60 | try { 61 | if (!srsName.equals(EPSG_4326)) { 62 | MathTransform transform = CRS.findMathTransform(getCrs(EPSG_4326), getCrs(srsName)); 63 | point = (Point) JTS.transform(point, transform); 64 | } 65 | return point; 66 | 67 | } catch (NoSuchAuthorityCodeException e) { 68 | throw new RuntimeException(e); 69 | } catch (FactoryException e) { 70 | throw new RuntimeException(e); 71 | } catch (MismatchedDimensionException e) { 72 | throw new RuntimeException(e); 73 | } catch (TransformException e) { 74 | throw new RuntimeException(e); 75 | } 76 | } 77 | 78 | public static Position convertToLatLon(double x, double y, String srsName) { 79 | GeometryFactory geometryFactory = new GeometryFactory(); 80 | Coordinate coordinate = new Coordinate(x, y); 81 | Point point = geometryFactory.createPoint(coordinate); 82 | 83 | try { 84 | if (!srsName.equals(EPSG_4326)) { 85 | MathTransform transform = CRS.findMathTransform(getCrs(EPSG_4326), getCrs(srsName)); 86 | point = (Point) JTS.transform(point, transform.inverse()); 87 | } 88 | return new Position(point.getY(), point.getX()); 89 | } catch (NoSuchAuthorityCodeException e) { 90 | throw new RuntimeException(e); 91 | } catch (FactoryException e) { 92 | throw new RuntimeException(e); 93 | } catch (MismatchedDimensionException e) { 94 | throw new RuntimeException(e); 95 | } catch (TransformException e) { 96 | throw new RuntimeException(e); 97 | } 98 | } 99 | 100 | } 101 | -------------------------------------------------------------------------------- /grumpy-projection/src/main/java/com/github/davidmoten/grumpy/projection/Projector.java: -------------------------------------------------------------------------------- 1 | package com.github.davidmoten.grumpy.projection; 2 | 3 | import java.awt.Point; 4 | import java.awt.geom.Point2D; 5 | import java.util.concurrent.atomic.AtomicBoolean; 6 | 7 | import com.github.davidmoten.grumpy.core.Position; 8 | 9 | import org.geotools.geometry.jts.JTS; 10 | import org.geotools.referencing.CRS; 11 | import org.locationtech.jts.geom.Coordinate; 12 | import org.locationtech.jts.geom.GeometryFactory; 13 | import org.opengis.geometry.MismatchedDimensionException; 14 | import org.opengis.referencing.FactoryException; 15 | import org.opengis.referencing.operation.MathTransform; 16 | import org.opengis.referencing.operation.TransformException; 17 | 18 | /** 19 | * Uses GeoTools and JTS libraries to perform transformations between coordinate 20 | * reference systems. 21 | * 22 | *

23 | * Set {@code Projector.forceXY = false} before instantiating any Projector if 24 | * you don't want the system property "org.geotools.referencing.forceXY" to be 25 | * set to truen (it will happen on first instantiation of a Projector by 26 | * default). 27 | * 28 | * @author dxm 29 | * 30 | */ 31 | public class Projector { 32 | 33 | private static final AtomicBoolean initialized = new AtomicBoolean(false); 34 | public static volatile boolean forceXY = true; 35 | 36 | private final ProjectorTarget target; 37 | private final ProjectorBounds bounds; 38 | 39 | private final MathTransform transform; 40 | private final GeometryFactory geometryFactory; 41 | 42 | public Projector(ProjectorBounds bounds, ProjectorTarget target) { 43 | // perform one time initialization in a thread safe way 44 | if (initialized.compareAndSet(false, true)) { 45 | if (forceXY) 46 | System.setProperty("org.geotools.referencing.forceXY", "true"); 47 | } 48 | this.target = target; 49 | this.bounds = bounds; 50 | try { 51 | transform = CRS.findMathTransform(FeatureUtil.getCrs(FeatureUtil.EPSG_4326), 52 | FeatureUtil.getCrs(bounds.getSrs())); 53 | 54 | } catch (FactoryException e) { 55 | throw new RuntimeException(e); 56 | } 57 | geometryFactory = new GeometryFactory(); 58 | } 59 | 60 | public ProjectorBounds getBounds() { 61 | return bounds; 62 | } 63 | 64 | public ProjectorTarget getTarget() { 65 | return target; 66 | } 67 | 68 | public org.locationtech.jts.geom.Point getGeometryPointInSrs(double lat, double lon) { 69 | Coordinate coordinate = new Coordinate(lon, lat); 70 | org.locationtech.jts.geom.Point point = geometryFactory.createPoint(coordinate); 71 | try { 72 | return (org.locationtech.jts.geom.Point) JTS.transform(point, transform); 73 | } catch (MismatchedDimensionException e) { 74 | throw new RuntimeException(e); 75 | } catch (TransformException e) { 76 | throw new RuntimeException(e); 77 | } 78 | } 79 | 80 | public org.locationtech.jts.geom.Point getGeometryPointInSrsRelativeTo(double lat, 81 | double lon, double relativeLat, double relativeLon, double relativeX, double relativeY) { 82 | 83 | double diffLon1 = lon - relativeLon; 84 | double diffLon2 = lon - relativeLon + 360; 85 | if (Math.abs(diffLon1) > Math.abs(diffLon2)) 86 | lon = lon + 360; 87 | double sign = Math.signum(lon - relativeLon); 88 | 89 | org.locationtech.jts.geom.Point point = getGeometryPointInSrs(lat, lon); 90 | double periodAtLat = periodAtLat(lat); 91 | double x = point.getX(); 92 | // makes the assumption that increasing lon = increasing X 93 | // which is probably valid for most common projections 94 | // TODO determine when invalid or handle 95 | if (sign >= 0) { 96 | while (x - periodAtLat >= relativeX) 97 | x -= periodAtLat; 98 | while (x < relativeX) 99 | x += periodAtLat; 100 | } else { 101 | while (x >= relativeX) 102 | x -= periodAtLat; 103 | while (x + periodAtLat < relativeX) 104 | x += periodAtLat; 105 | } 106 | 107 | return createPoint(x, point.getY()); 108 | } 109 | 110 | public org.locationtech.jts.geom.Point createPoint(double x, double y) { 111 | return geometryFactory.createPoint(new Coordinate(x, y)); 112 | } 113 | 114 | public double periodAtLat(double lat) { 115 | return getGeometryPointInSrs(lat, 180).getX() - getGeometryPointInSrs(lat, -180).getX(); 116 | } 117 | 118 | public Point2D.Double getTargetPoint(org.locationtech.jts.geom.Point point) { 119 | double proportionX = (point.getX() - bounds.getMinX()) 120 | / (bounds.getMaxX() - bounds.getMinX()); 121 | double proportionY = (bounds.getMaxY() - point.getY()) 122 | / (bounds.getMaxY() - bounds.getMinY()); 123 | double x = proportionX * target.getWidth(); 124 | double y = proportionY * target.getHeight(); 125 | return new Point2D.Double(x, y); 126 | } 127 | 128 | public org.locationtech.jts.geom.Point getFirstXAfter(Projector projector, double lat, 129 | double lon, double x) { 130 | org.locationtech.jts.geom.Point point = projector.getGeometryPointInSrs(lat, lon); 131 | double x2 = point.getX(); 132 | double periodX = periodAtLat(lat); 133 | while (x2 - periodX >= x) 134 | x2 -= periodX; 135 | while (x2 + periodX < x) 136 | x2 += periodX; 137 | return createPoint(x2, point.getY()); 138 | } 139 | 140 | public Point toPoint(double lat, double lon) { 141 | Point2D point2D = toPoint2D(lat, lon); 142 | Point p = new Point(); 143 | p.x = (int) Math.round(point2D.getX()); 144 | p.y = (int) Math.round(point2D.getY()); 145 | return p; 146 | } 147 | 148 | public Point2D.Double toPointInSrs(double lat, double lon) { 149 | org.locationtech.jts.geom.Point point = getGeometryPointInSrs(lat, lon); 150 | return new Point2D.Double(point.getX(), point.getY()); 151 | } 152 | 153 | public Point2D.Double toPoint2D(double lat, double lon) { 154 | Coordinate coordinate = new Coordinate(lon, lat); 155 | org.locationtech.jts.geom.Point point = geometryFactory.createPoint(coordinate); 156 | try { 157 | point = (org.locationtech.jts.geom.Point) JTS.transform(point, transform); 158 | 159 | double proportionX; 160 | if (point.getX() > bounds.getMaxX() || point.getX() < bounds.getMinX()) { 161 | // assume the maxX occurs at longitude 180 (true for EPSG 3857 162 | // spherical mercator) but maybe not true for other projections? 163 | Coordinate c = new Coordinate(180, 0); 164 | org.locationtech.jts.geom.Point pt = (org.locationtech.jts.geom.Point) JTS 165 | .transform(geometryFactory.createPoint(c), transform); 166 | double maximumX = pt.getX(); 167 | if (point.getX() > bounds.getMaxX()) 168 | proportionX = (point.getX() - 2 * maximumX - bounds.getMinX()) 169 | / (bounds.getMaxX() - bounds.getMinX()); 170 | else 171 | proportionX = (point.getX() + 2 * maximumX - bounds.getMinX()) 172 | / (bounds.getMaxX() - bounds.getMinX()); 173 | } else { 174 | proportionX = (point.getX() - bounds.getMinX()) 175 | / (bounds.getMaxX() - bounds.getMinX()); 176 | } 177 | double proportionY = (bounds.getMaxY() - point.getY()) 178 | / (bounds.getMaxY() - bounds.getMinY()); 179 | Point2D.Double point2D = new Point2D.Double(proportionX * target.getWidth(), 180 | proportionY * target.getHeight()); 181 | return point2D; 182 | } catch (MismatchedDimensionException e) { 183 | throw new RuntimeException(e); 184 | } catch (TransformException e) { 185 | throw new RuntimeException(e); 186 | } 187 | } 188 | 189 | public Position toPosition(double targetX, double targetY) { 190 | double proportionX = targetX / target.getWidth(); 191 | double proportionY = targetY / target.getHeight(); 192 | double x = proportionX * (bounds.getMaxX() - bounds.getMinX()) + bounds.getMinX(); 193 | double y = bounds.getMaxY() - proportionY * (bounds.getMaxY() - bounds.getMinY()); 194 | Coordinate coordinate = new Coordinate(x, y); 195 | org.locationtech.jts.geom.Point point = geometryFactory.createPoint(coordinate); 196 | try { 197 | point = (org.locationtech.jts.geom.Point) JTS.transform(point, transform.inverse()); 198 | } catch (MismatchedDimensionException e) { 199 | throw new RuntimeException(e); 200 | } catch (TransformException e) { 201 | throw new RuntimeException(e); 202 | } 203 | return new Position(point.getY(), point.getX()); 204 | } 205 | 206 | public Position toPositionFromSrs(double x, double y) { 207 | return FeatureUtil.convertToLatLon(x, y, bounds.getSrs()); 208 | } 209 | 210 | @Override 211 | public String toString() { 212 | return "ProjectorImpl [target=" + target + ", bounds=" + bounds + "]"; 213 | } 214 | 215 | } 216 | -------------------------------------------------------------------------------- /grumpy-projection/src/main/java/com/github/davidmoten/grumpy/projection/ProjectorBounds.java: -------------------------------------------------------------------------------- 1 | package com.github.davidmoten.grumpy.projection; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | public class ProjectorBounds { 7 | private final double minX; 8 | private final double minY; 9 | private final double maxX; 10 | private final double maxY; 11 | private final String srs; 12 | 13 | public String getSrs() { 14 | return srs; 15 | } 16 | 17 | public ProjectorBounds(String srs, double minX, double minY, double maxX, double maxY) { 18 | this.minX = minX; 19 | this.minY = minY; 20 | this.maxX = maxX; 21 | this.maxY = maxY; 22 | this.srs = srs; 23 | } 24 | 25 | public double getMinX() { 26 | return minX; 27 | } 28 | 29 | public double getMinY() { 30 | return minY; 31 | } 32 | 33 | public double getMaxX() { 34 | return maxX; 35 | } 36 | 37 | public double getMaxY() { 38 | return maxY; 39 | } 40 | 41 | public double getSizeX() { 42 | return this.maxX - this.minX; 43 | } 44 | 45 | public double getSizeY() { 46 | return maxY - minY; 47 | } 48 | 49 | public List splitHorizontally() { 50 | List list = new ArrayList(); 51 | list.add(new ProjectorBounds(srs, minX, minY, minX + getSizeX() / 2, maxY)); 52 | list.add(new ProjectorBounds(srs, minX + getSizeX() / 2, minY, maxX, maxY)); 53 | return list; 54 | } 55 | 56 | public List splitVertically() { 57 | List list = new ArrayList(); 58 | list.add(new ProjectorBounds(srs, minX, minY, maxX, minY + getSizeY() / 2)); 59 | list.add(new ProjectorBounds(srs, minX, minY + getSizeY() / 2, maxX, maxY)); 60 | return list; 61 | } 62 | 63 | public List quarter() { 64 | List list = new ArrayList(); 65 | for (ProjectorBounds b : splitHorizontally()) 66 | list.addAll(b.splitVertically()); 67 | return list; 68 | } 69 | 70 | @Override 71 | public String toString() { 72 | return "ProjectorBounds [srs=" + srs + ", minX=" + minX + ", minY=" + minY + ", maxX=" 73 | + maxX + ", maxY=" + maxY + "]"; 74 | } 75 | 76 | } 77 | -------------------------------------------------------------------------------- /grumpy-projection/src/main/java/com/github/davidmoten/grumpy/projection/ProjectorTarget.java: -------------------------------------------------------------------------------- 1 | package com.github.davidmoten.grumpy.projection; 2 | 3 | public class ProjectorTarget { 4 | 5 | private final int width; 6 | private final int height; 7 | 8 | public ProjectorTarget(int width, int height) { 9 | this.width = width; 10 | this.height = height; 11 | } 12 | 13 | public int getWidth() { 14 | return width; 15 | } 16 | 17 | public int getHeight() { 18 | return height; 19 | } 20 | 21 | @Override 22 | public String toString() { 23 | return "ProjectorTarget [width=" + width + ", height=" + height + "]"; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /grumpy-projection/src/main/java/com/github/davidmoten/grumpy/util/NearBSpline.java: -------------------------------------------------------------------------------- 1 | package com.github.davidmoten.grumpy.util; 2 | 3 | import java.awt.Rectangle; 4 | import java.awt.Shape; 5 | import java.awt.geom.AffineTransform; 6 | import java.awt.geom.CubicCurve2D; 7 | import java.awt.geom.GeneralPath; 8 | import java.awt.geom.Line2D; 9 | import java.awt.geom.PathIterator; 10 | import java.awt.geom.Point2D; 11 | import java.awt.geom.QuadCurve2D; 12 | import java.awt.geom.Rectangle2D; 13 | 14 | /** 15 | * A shape that is almost a B-spline curve but not necessarily exactly. If the 16 | * shape has n points, the following things are guaranteed: 17 | *

    18 | *
  • n == 2: the shape is a straight line, 19 | *
  • n == 3: the shape is a quadratic curve, 20 | *
  • n == 4: the shape is a cubic curve, 21 | *
  • n >= 5: the shape consists of segments, which are joined together 22 | * so that the first derivate is continuous at intermittent points 23 | *
24 | * 25 | */ 26 | public class NearBSpline implements Shape, Cloneable { 27 | /** holds the combination of curve and line segments */ 28 | private GeneralPath path; 29 | 30 | /** the last segment added to the path */ 31 | private Shape lastPart; 32 | 33 | /** the individual segments of the path */ 34 | private Shape[] segments; 35 | 36 | /** 37 | * Creates a new, empty b-spline 38 | */ 39 | public NearBSpline() { 40 | } 41 | 42 | /** 43 | * Creates a new b-spline with the given control points 44 | * 45 | * @param points 46 | * the control points for the curve 47 | */ 48 | public NearBSpline(Point2D[] points) { 49 | setLine(points); 50 | } 51 | 52 | /** 53 | * Sets the curve using control points given in integer precision 54 | * 55 | * @param points 56 | * the control points for the curve 57 | */ 58 | public void setLine(int... points) { 59 | double[] pd = new double[points.length]; 60 | for (int i = 0; i < points.length; i++) { 61 | pd[i] = points[i]; 62 | } 63 | setLine(pd); 64 | } 65 | 66 | /** 67 | * Sets the curve using control points given in double precision 68 | * 69 | * @param points 70 | * the control points for the curve 71 | */ 72 | public void setLine(double... points) { 73 | path = new GeneralPath(); 74 | path.moveTo((float) points[0], (float) points[1]); 75 | segments = new Shape[points.length / 2 - 1]; 76 | 77 | for (int i = 2; i < points.length;) { 78 | switch (points.length - i) { 79 | case 2: 80 | lastPart = new Line2D.Float((float) path.getCurrentPoint().getX(), (float) path 81 | .getCurrentPoint().getY(), (float) points[i], (float) points[i + 1]); 82 | path.append(lastPart, true); 83 | segments[i / 2 - 1] = lastPart; 84 | i += 2; 85 | break; 86 | case 4: 87 | lastPart = new QuadCurve2D.Float((float) path.getCurrentPoint().getX(), 88 | (float) path.getCurrentPoint().getY(), (float) points[i], 89 | (float) points[i + 1], (float) points[i + 2], (float) points[i + 3]); 90 | path.append(lastPart, true); 91 | segments[i / 2 - 1] = lastPart; 92 | segments[i / 2] = lastPart; 93 | i += 4; 94 | break; 95 | case 6: 96 | lastPart = new CubicCurve2D.Double(path.getCurrentPoint().getX(), path 97 | .getCurrentPoint().getY(), points[i], points[i + 1], points[i + 2], 98 | points[i + 3], points[i + 4], points[i + 5]); 99 | path.append(lastPart, true); 100 | segments[i / 2 - 1] = lastPart; 101 | segments[i / 2] = lastPart; 102 | segments[i / 2 + 1] = lastPart; 103 | i += 6; 104 | break; 105 | default: // use two points and add one extra between 2nd and 3rd 106 | float x = (float) (points[i + 2] + points[i + 4]) / 2F; 107 | float y = (float) (points[i + 3] + points[i + 5]) / 2F; 108 | 109 | lastPart = new CubicCurve2D.Double(path.getCurrentPoint().getX(), path 110 | .getCurrentPoint().getY(), points[i], points[i + 1], points[i + 2], 111 | points[i + 3], x, y); 112 | 113 | path.append(lastPart, true); 114 | segments[i / 2 - 1] = lastPart; 115 | segments[i / 2] = lastPart; 116 | 117 | i += 4; 118 | } 119 | } 120 | } 121 | 122 | /** 123 | * Sets the curve using control points given as Point2D objects 124 | * 125 | * @param points 126 | * the control points for the curve 127 | */ 128 | public void setLine(Point2D[] points) { 129 | double[] pd = new double[points.length * 2]; 130 | for (int i = 0; i < points.length; i++) { 131 | Point2D p = points[i]; 132 | pd[i * 2] = p.getX(); 133 | pd[i * 2 + 1] = p.getY(); 134 | } 135 | setLine(pd); 136 | } 137 | 138 | public Rectangle getBounds() { 139 | return getBounds2D().getBounds(); 140 | } 141 | 142 | public Rectangle2D getBounds2D() { 143 | return path.getBounds2D(); 144 | } 145 | 146 | public boolean contains(double x, double y) { 147 | return path.contains(x, y); 148 | } 149 | 150 | public boolean contains(Point2D p) { 151 | return path.contains(p); 152 | } 153 | 154 | public boolean intersects(double x, double y, double w, double h) { 155 | return path.intersects(x, y, w, h); 156 | } 157 | 158 | public boolean intersects(Rectangle2D r) { 159 | return path.intersects(r); 160 | } 161 | 162 | public boolean contains(double x, double y, double w, double h) { 163 | return path.contains(x, y, w, h); 164 | } 165 | 166 | public boolean contains(Rectangle2D r) { 167 | return path.contains(r); 168 | } 169 | 170 | public PathIterator getPathIterator(AffineTransform at) { 171 | return path.getPathIterator(at); 172 | } 173 | 174 | public PathIterator getPathIterator(AffineTransform at, double flatness) { 175 | return path.getPathIterator(at, flatness); 176 | } 177 | 178 | /** 179 | * Returns the last piece of this curve, which should be the most likely 180 | * thing to intercept with rectangles at the end. 181 | * 182 | * @return a Line2D, QuadCurve2D, or CubicCurve2D object 183 | */ 184 | public Shape getLastPart() { 185 | return lastPart; 186 | } 187 | 188 | /** 189 | * Return the segment of the path that should be adjacent to the control 190 | * point at the given index. 191 | * 192 | * @param i 193 | * the index of the control point 194 | * @return the path segment adjacent to the control point (a Line2D, 195 | * QuadCurve2D, or CubicCurve2D object) 196 | */ 197 | public Shape getSegment(int i) { 198 | return segments[i]; 199 | } 200 | 201 | public GeneralPath getPath() { 202 | return path; 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /grumpy-projection/src/main/resources/epsg/EPSG_102100.txt: -------------------------------------------------------------------------------- 1 | PROJCS["WGS 84 / Pseudo-Mercator", 2 | GEOGCS["Popular Visualisation CRS", 3 | DATUM["Popular_Visualisation_Datum", 4 | SPHEROID["Popular Visualisation Sphere",6378137,0,AUTHORITY["EPSG","7059"]], 5 | TOWGS84[0,0,0,0,0,0,0], 6 | AUTHORITY["EPSG","6055"]], 7 | PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]], 8 | UNIT["degree",0.01745329251994328,AUTHORITY["EPSG","9122"]], 9 | AUTHORITY["EPSG","4055"]], 10 | UNIT["metre",1,AUTHORITY["EPSG","9001"]], 11 | PROJECTION["Mercator_1SP"], 12 | PARAMETER["central_meridian",0], 13 | PARAMETER["scale_factor",1], 14 | PARAMETER["false_easting",0], 15 | PARAMETER["false_northing",0], 16 | AUTHORITY["EPSG","3785"], 17 | AXIS["X",EAST], 18 | AXIS["Y",NORTH]] -------------------------------------------------------------------------------- /grumpy-projection/src/main/resources/epsg/EPSG_900913.txt: -------------------------------------------------------------------------------- 1 | PROJCS["WGS84 / Google Mercator", 2 | GEOGCS["WGS 84", 3 | DATUM["World Geodetic System 1984", 4 | SPHEROID["WGS 84", 6378137.0, 298.257223563, AUTHORITY["EPSG","7030"]], 5 | AUTHORITY["EPSG","6326"]], 6 | PRIMEM["Greenwich", 0.0, AUTHORITY["EPSG","8901"]], 7 | UNIT["degree", 0.017453292519943295], 8 | AXIS["Longitude", EAST], 9 | AXIS["Latitude", NORTH], 10 | AUTHORITY["EPSG","4326"]], 11 | PROJECTION["Mercator_1SP"], 12 | PARAMETER["semi_major", 6378137.0], 13 | PARAMETER["semi_minor", 6378137.0], 14 | PARAMETER["latitude_of_origin", 0.0], 15 | PARAMETER["central_meridian", 0.0], 16 | PARAMETER["scale_factor", 1.0], 17 | PARAMETER["false_easting", 0.0], 18 | PARAMETER["false_northing", 0.0], 19 | UNIT["m", 1.0], 20 | AXIS["x", EAST], 21 | AXIS["y", NORTH], 22 | AUTHORITY["EPSG","900913"]] -------------------------------------------------------------------------------- /grumpy-projection/src/test/java/com/github/davidmoten/grumpy/ProjectorTest.java: -------------------------------------------------------------------------------- 1 | package com.github.davidmoten.grumpy; 2 | 3 | import com.github.davidmoten.grumpy.projection.Projector; 4 | import com.github.davidmoten.grumpy.projection.ProjectorBounds; 5 | import com.github.davidmoten.grumpy.projection.ProjectorTarget; 6 | import org.junit.Test; 7 | import org.locationtech.jts.geom.Point; 8 | 9 | import java.awt.geom.Point2D.Double; 10 | 11 | import static org.junit.Assert.assertEquals; 12 | 13 | public class ProjectorTest { 14 | 15 | private static final double PRECISION = 1E-7; 16 | 17 | @Test 18 | public void testTransformWrapping() { 19 | ProjectorTarget target = new ProjectorTarget(300, 200); 20 | ProjectorBounds bounds = new ProjectorBounds("EPSG:3857", 18924313.4349, -4865942.2795, 21 | -18924313.4349, -3503549.8435); 22 | Projector projector = new Projector(bounds, target); 23 | Point p = projector.getGeometryPointInSrs(-35, 140); 24 | Double point = projector.getTargetPoint(p); 25 | System.out.println("x1=" + p.getX() + " point=" + point); 26 | assertEquals(26.470588235578038, point.getX(), PRECISION); 27 | assertEquals(96.93701801560695, point.getY(), PRECISION); 28 | Point p2 = projector 29 | .getGeometryPointInSrsRelativeTo(-35, 141, -35, 140, p.getX(), p.getY()); 30 | System.out.println("x2=" + p2.getX()); 31 | assertEquals(1.5696048201851575E7, p2.getX(), PRECISION); 32 | double x3 = p.getX() - projector.periodAtLat(-35); 33 | Double point3 = projector.getTargetPoint(projector.createPoint(x3, p.getY())); 34 | System.out.println("x3=" + x3 + " point=" + point3); 35 | double x4 = p.getX() + projector.periodAtLat(-35); 36 | Double point4 = projector.getTargetPoint(projector.createPoint(x4, p.getY())); 37 | System.out.println("x4=" + x4 + " point=" + point4); 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 4.0.0 5 | 6 | 7 | com.github.davidmoten 8 | sonatype-parent 9 | 0.2.3 10 | 11 | 12 | grumpy 13 | 0.4.9-SNAPSHOT 14 | 15 | ${project.artifactId} 16 | OGC tools including WMS server 17 | pom 18 | 19 | http://github.com/davidmoten/grumpy 20 | 21 | 22 | 1.8 23 | 1.7.36 24 | 28.2 25 | 26 | 9.4.55.v20240627 27 | 4.13.2 28 | 2.7 29 | 2.11 30 | 3.0.5 31 | 3.11.2 32 | 3.26.0 33 | 2.1 34 | 2.0 35 | 3.9.0 36 | 3.6.0 37 | 3.2.1 38 | 3.21.0 39 | 2.2 40 | ${project.build.directory}/target/coverage-reports 41 | 3.4.0 42 | scm:git:https://github.com/davidmoten/grumpy.git 43 | 0.1.7 44 | 45 | 46 | 47 | 48 | 49 | The Apache Software License, Version 2.0 50 | http://www.apache.org/licenses/LICENSE-2.0.txt 51 | repo 52 | A business-friendly OSS license 53 | 54 | 55 | 56 | 57 | Travis 58 | https://travis-ci.org/davidmoten/grumpy 59 | 60 | 61 | 62 | GitHub 63 | https://github.com/davidmoten/grumpy/issues 64 | 65 | 66 | 2013 67 | 68 | 69 | dave 70 | Dave Moten 71 | https://github.com/davidmoten/ 72 | 73 | architect 74 | developer 75 | 76 | +10 77 | 78 | 79 | 80 | 81 | ${scm.url} 82 | ${scm.url} 83 | ${scm.url} 84 | HEAD 85 | 86 | 87 | 88 | grumpy-core 89 | grumpy-projection 90 | grumpy-ogc 91 | grumpy-ogc-layers 92 | wms-demo 93 | grumpy-app 94 | 95 | 96 | 97 | 98 | 99 | maven-compiler-plugin 100 | 3.14.0 101 | 102 | ${maven.compiler.target} 103 | ${maven.compiler.target} 104 | 105 | 106 | 107 | org.jacoco 108 | jacoco-maven-plugin 109 | 0.8.13 110 | 111 | 112 | 113 | prepare-agent 114 | 115 | 116 | 117 | report 118 | test 119 | 120 | report 121 | 122 | 123 | 125 | 126 | 127 | 128 | maven-site-plugin 129 | ${m3.site.version} 130 | 131 | 132 | attach-descriptor 133 | 134 | attach-descriptor 135 | 136 | 137 | 138 | 139 | 140 | org.apache.maven.plugins 141 | maven-javadoc-plugin 142 | ${javadoc.version} 143 | 144 | 145 | attach-javadocs 146 | 147 | jar 148 | 149 | 150 | 151 | 152 | 153 | -Xdoclint:none 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 163 | 164 | org.apache.maven.plugins 165 | maven-jxr-plugin 166 | ${jxr.version} 167 | 168 | true 169 | 170 | 171 | 172 | org.codehaus.mojo 173 | cobertura-maven-plugin 174 | ${cobertura.version} 175 | 176 | false 177 | 178 | 179 | 180 | org.apache.maven.plugins 181 | maven-pmd-plugin 182 | ${pmd.version} 183 | 184 | ${maven.compiler.target} 185 | true 186 | 187 | 188 | 189 | org.codehaus.mojo 190 | findbugs-maven-plugin 191 | ${findbugs.version} 192 | 193 | true 194 | Max 195 | 196 | 197 | 198 | 199 | org.codehaus.mojo 200 | jdepend-maven-plugin 201 | ${jdepend.version} 202 | 203 | 204 | org.apache.maven.plugins 205 | maven-project-info-reports-plugin 206 | ${project.info.version} 207 | 208 | false 209 | false 210 | 211 | 212 | 213 | org.codehaus.mojo 214 | taglist-maven-plugin 215 | ${taglist.version} 216 | 217 | 218 | org.apache.maven.plugins 219 | maven-javadoc-plugin 220 | ${javadoc.version} 221 | 222 | true 223 | 224 | 225 | 227 | 230 | 231 | 232 | 233 | 234 | 235 | grumpy-site 236 | http://davidmoten.github.io/grumpy 237 | 238 | 239 | 240 | 241 | 242 | geotools 243 | geotools 244 | https://repo.osgeo.org/repository/release 245 | 246 | 247 | 248 | 249 | -------------------------------------------------------------------------------- /src/docs/craft.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidmoten/grumpy/7a956d47a15e53420afc64e4d9999a20814687d3/src/docs/craft.png -------------------------------------------------------------------------------- /src/docs/demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidmoten/grumpy/7a956d47a15e53420afc64e4d9999a20814687d3/src/docs/demo.png -------------------------------------------------------------------------------- /src/docs/demo2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidmoten/grumpy/7a956d47a15e53420afc64e4d9999a20814687d3/src/docs/demo2.png -------------------------------------------------------------------------------- /src/docs/demo3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidmoten/grumpy/7a956d47a15e53420afc64e4d9999a20814687d3/src/docs/demo3.png -------------------------------------------------------------------------------- /src/docs/demo4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidmoten/grumpy/7a956d47a15e53420afc64e4d9999a20814687d3/src/docs/demo4.png -------------------------------------------------------------------------------- /wms-demo/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 4.0.0 5 | 6 | 7 | com.github.davidmoten 8 | grumpy 9 | 0.4.9-SNAPSHOT 10 | 11 | 12 | wms-demo 13 | 14 | ${project.artifactId} 15 | OGC tools including WMS server 16 | war 17 | 18 | http://github.com/davidmoten/grumpy 19 | 20 | 21 | 22 | com.github.davidmoten 23 | grumpy-ogc 24 | ${project.parent.version} 25 | 26 | 27 | 28 | 29 | com.github.davidmoten 30 | grumpy-ogc-layers 31 | ${project.parent.version} 32 | 33 | 34 | 35 | javax.servlet 36 | servlet-api 37 | 2.5 38 | provided 39 | 40 | 41 | 42 | log4j 43 | log4j 44 | 1.2.17.norce 45 | 46 | 47 | 48 | org.slf4j 49 | slf4j-log4j12 50 | ${slf4j.version} 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | org.apache.maven.plugins 59 | maven-war-plugin 60 | ${war.plugin.version} 61 | 62 | 63 | maven-compiler-plugin 64 | 3.14.0 65 | 66 | ${maven.compiler.target} 67 | ${maven.compiler.target} 68 | 69 | 70 | 71 | org.eclipse.jetty 72 | jetty-maven-plugin 73 | ${jetty.plugin.version} 74 | 75 | 10 76 | foo 77 | 9998 78 | 79 | /${project.artifactId} 80 | 81 | 82 | 83 | 84 | 85 | 86 | org.apache.maven.plugins 87 | maven-deploy-plugin 88 | 3.1.4 89 | 90 | true 91 | 92 | 93 | 94 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /wms-demo/src/main/java/com/github/davidmoten/grumpy/wms/demo/CustomLayer.java: -------------------------------------------------------------------------------- 1 | package com.github.davidmoten.grumpy.wms.demo; 2 | 3 | import static com.github.davidmoten.grumpy.core.Position.position; 4 | import static com.github.davidmoten.grumpy.wms.RendererUtil.draw; 5 | import static com.github.davidmoten.grumpy.wms.RendererUtil.fill; 6 | import static com.github.davidmoten.grumpy.wms.RendererUtil.toPathGreatCircle; 7 | 8 | import java.awt.Color; 9 | import java.awt.Font; 10 | import java.awt.Graphics2D; 11 | import java.awt.Point; 12 | import java.awt.geom.GeneralPath; 13 | import java.util.ArrayList; 14 | import java.util.Date; 15 | import java.util.List; 16 | 17 | import org.slf4j.Logger; 18 | import org.slf4j.LoggerFactory; 19 | 20 | import com.github.davidmoten.grumpy.core.Position; 21 | import com.github.davidmoten.grumpy.projection.Projector; 22 | import com.github.davidmoten.grumpy.wms.Layer; 23 | import com.github.davidmoten.grumpy.wms.LayerFeatures; 24 | import com.github.davidmoten.grumpy.wms.RendererUtil; 25 | import com.github.davidmoten.grumpy.wms.WmsRequest; 26 | import com.github.davidmoten.grumpy.wms.WmsUtil; 27 | 28 | public class CustomLayer implements Layer { 29 | 30 | private static final Logger log = LoggerFactory 31 | .getLogger(CustomLayer.class); 32 | 33 | private static final String PLACE = "Canberra"; 34 | private static final double PLACE_LAT = -35.3075; 35 | private static final double PLACE_LON = 149.1244; 36 | private final List box; 37 | 38 | private final LayerFeatures features; 39 | 40 | public CustomLayer() { 41 | // prepare a box around place 42 | box = new ArrayList(); 43 | box.add(position(PLACE_LAT - 2, PLACE_LON - 4)); 44 | box.add(position(PLACE_LAT + 2, PLACE_LON - 4)); 45 | box.add(position(PLACE_LAT + 2, PLACE_LON + 4)); 46 | box.add(position(PLACE_LAT - 2, PLACE_LON + 4)); 47 | box.add(position(PLACE_LAT - 2, PLACE_LON - 4)); 48 | 49 | features = LayerFeatures.builder().name("Custom").crs("EPSG:4326") 50 | .crs("EPSG:3857").queryable().build(); 51 | } 52 | 53 | @Override 54 | public void render(Graphics2D g, WmsRequest request) { 55 | 56 | log.info("scale=" + WmsUtil.getScale(request)); 57 | 58 | Projector projector = WmsUtil.getProjector(request); 59 | // only start the logic to load the data and schedule refreshes once 60 | // get the limits of the request box in lats and longs so we can use an 61 | // rtree 62 | Position min = projector.toPositionFromSrs(request.getBounds() 63 | .getMinX(), request.getBounds().getMinY()); 64 | Position max = projector.toPositionFromSrs(request.getBounds() 65 | .getMaxX(), request.getBounds().getMaxY()); 66 | log.info("min=" + min + ", max=" + max); 67 | 68 | RendererUtil.useAntialiasing(g); 69 | 70 | // get the box around place as a shape 71 | List shapes = toPathGreatCircle(projector, box); 72 | 73 | // fill the box with white 74 | // transparency is deferred to the wms client framework 75 | g.setColor(Color.white); 76 | fill(g, shapes); 77 | 78 | // draw border in blue 79 | g.setColor(Color.blue); 80 | draw(g, shapes); 81 | 82 | // label place 83 | Point p = projector.toPoint(PLACE_LAT, PLACE_LON); 84 | g.setColor(Color.RED); 85 | g.setFont(g.getFont().deriveFont(24.0f).deriveFont(Font.BOLD)); 86 | g.drawString(PLACE, p.x + 5, p.y); 87 | 88 | } 89 | 90 | @Override 91 | public String getInfo(Date time, WmsRequest request, Point point, 92 | String mimeType) { 93 | 94 | // if user clicks within Canberra box then return some info, otherwise 95 | // return blank string 96 | 97 | Projector projector = WmsUtil.getProjector(request); 98 | Position position = projector.toPosition(point.x, point.y); 99 | 100 | if (position.isWithin(box)) 101 | return "
" 102 | + "

Canberra is the capital city of Australia. With a population of 381,488, it is Australia's largest inland city and the eighth-largest city overall.

" 103 | + "" 104 | + "
"; 105 | else 106 | return ""; 107 | } 108 | 109 | @Override 110 | public LayerFeatures getFeatures() { 111 | return features; 112 | } 113 | 114 | } 115 | -------------------------------------------------------------------------------- /wms-demo/src/main/java/com/github/davidmoten/grumpy/wms/demo/FiddleLayer.java: -------------------------------------------------------------------------------- 1 | package com.github.davidmoten.grumpy.wms.demo; 2 | 3 | import static com.github.davidmoten.grumpy.core.Position.position; 4 | import static com.github.davidmoten.grumpy.wms.WmsUtil.getProjector; 5 | 6 | import java.awt.Color; 7 | import java.awt.Graphics2D; 8 | import java.awt.Point; 9 | import java.awt.Rectangle; 10 | import java.awt.geom.GeneralPath; 11 | import java.util.Date; 12 | import java.util.List; 13 | 14 | import com.github.davidmoten.grumpy.core.Position; 15 | import com.github.davidmoten.grumpy.function.Function; 16 | import com.github.davidmoten.grumpy.projection.Projector; 17 | import com.github.davidmoten.grumpy.wms.Layer; 18 | import com.github.davidmoten.grumpy.wms.LayerFeatures; 19 | import com.github.davidmoten.grumpy.wms.RendererUtil; 20 | import com.github.davidmoten.grumpy.wms.WmsRequest; 21 | import com.github.davidmoten.grumpy.wms.WmsUtil; 22 | import com.github.davidmoten.grumpy.wms.reduction.RectangleSampler; 23 | import com.github.davidmoten.grumpy.wms.reduction.RectangleSamplerCorners; 24 | import com.github.davidmoten.grumpy.wms.reduction.Reducer; 25 | import com.github.davidmoten.grumpy.wms.reduction.ValueRenderer; 26 | 27 | public class FiddleLayer implements Layer { 28 | 29 | private final LayerFeatures features; 30 | 31 | public FiddleLayer() { 32 | features = LayerFeatures.builder().name("Fiddle").crs("EPSG:4326").crs("EPSG:3857").build(); 33 | } 34 | 35 | @Override 36 | public void render(Graphics2D g, WmsRequest request) { 37 | Position centre = position(35, -40); 38 | Projector projector = getProjector(request); 39 | 40 | int radiusKm = 8000; 41 | 42 | Function function = createValueFunction(centre, radiusKm); 43 | ValueRenderer valueRenderer = createValueRenderer(); 44 | RectangleSampler sampler = new RectangleSamplerCorners(); 45 | Reducer.render(g, function, projector, sampler, valueRenderer); 46 | 47 | } 48 | 49 | private ValueRenderer createValueRenderer() { 50 | return new ValueRenderer() { 51 | @Override 52 | public void render(Graphics2D g, Projector projector, Rectangle region, Boolean inRegion) { 53 | if (inRegion) { 54 | List positions = WmsUtil.getBorder(projector, region); 55 | List shapes = RendererUtil.toPath(projector, positions); 56 | g.setColor(new Color(0, 150, 0)); 57 | RendererUtil.fill(g, shapes); 58 | } 59 | } 60 | }; 61 | } 62 | 63 | private Function createValueFunction(final Position centre, 64 | final double radiusKm) { 65 | return new Function() { 66 | @Override 67 | public Boolean apply(Position p) { 68 | return centre.getDistanceToKm(p) <= radiusKm; 69 | } 70 | }; 71 | } 72 | 73 | @Override 74 | public String getInfo(Date time, WmsRequest request, Point point, String mimeType) { 75 | return null; 76 | } 77 | 78 | @Override 79 | public LayerFeatures getFeatures() { 80 | return features; 81 | } 82 | 83 | } 84 | -------------------------------------------------------------------------------- /wms-demo/src/main/java/com/github/davidmoten/grumpy/wms/demo/WmsServlet.java: -------------------------------------------------------------------------------- 1 | package com.github.davidmoten.grumpy.wms.demo; 2 | 3 | import java.io.IOException; 4 | 5 | import javax.servlet.ServletException; 6 | import javax.servlet.http.HttpServlet; 7 | import javax.servlet.http.HttpServletRequest; 8 | import javax.servlet.http.HttpServletResponse; 9 | 10 | import com.github.davidmoten.grumpy.wms.Capabilities; 11 | import com.github.davidmoten.grumpy.wms.WmsServletRequestProcessor; 12 | import com.github.davidmoten.grumpy.wms.layer.darkness.DarknessLayer; 13 | 14 | public final class WmsServlet extends HttpServlet { 15 | 16 | private static final long serialVersionUID = 1518113833457077766L; 17 | 18 | private static final String SERVICE_TITLE = "Custom OGC Services"; 19 | private static final String SERVICE_NAME = "CustomOGC"; 20 | private static final String SERVICE_ABSTRACT = "Custom OGC WMS services including Custom, Fiddle and Darkness layers"; 21 | 22 | private WmsServletRequestProcessor processor; 23 | 24 | @Override 25 | protected void doGet(HttpServletRequest req, HttpServletResponse resp) 26 | throws ServletException, IOException { 27 | String serviceBaseUrl = req.getRequestURL().toString(); 28 | load(serviceBaseUrl); 29 | 30 | // use the processor to handle requests 31 | processor.doGet(req, resp); 32 | } 33 | 34 | private void load(String serviceBaseUrl) { 35 | if (processor != null) 36 | return; 37 | // instantiate the layers 38 | CustomLayer custom = new CustomLayer(); 39 | DarknessLayer darkness = new DarknessLayer(); 40 | FiddleLayer fiddle = new FiddleLayer(); 41 | 42 | // setup the capabilities of the service which will extract features 43 | // from the layers to fill in defaults for the layer fields in generated 44 | // capabilities.xml 45 | Capabilities cap = Capabilities.builder() 46 | // set service name 47 | .serviceName(SERVICE_NAME) 48 | // set service title 49 | .serviceTitle(SERVICE_TITLE) 50 | // set service abstract 51 | .serviceAbstract(SERVICE_ABSTRACT) 52 | // 53 | .serviceBaseUrl(serviceBaseUrl) // 54 | // add image format 55 | .imageFormat("image/png") 56 | // add info format 57 | .infoFormat("text/html") 58 | // add custom layer 59 | .layerFeatures(custom) 60 | // add darkness layer 61 | .layerFeatures(darkness) 62 | // add fiddle layer 63 | .layerFeatures(fiddle) 64 | // build caps 65 | .build(); 66 | 67 | // initialize the request processor 68 | processor = WmsServletRequestProcessor.builder() 69 | // capabilities 70 | .capabilities(cap) 71 | // or use 72 | // .capabilitiesFromClasspath("/wms-capabilities.xml") 73 | // set image cache size 74 | .imageCache(200) 75 | // add custom layer as cached 76 | .addCachedLayer("Custom", custom) 77 | // add darkness, not cached 78 | .addLayer("Darkness", darkness) 79 | // add fiddles layer 80 | .addLayer("Fiddle", fiddle) 81 | // build it up 82 | .build(); 83 | } 84 | 85 | } 86 | -------------------------------------------------------------------------------- /wms-demo/src/main/resources/wms-capabilities.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | Custom WMS services 9 | 10 | Custom WMS Group 11 | 12 | 13 | Demonstration of custom rendered wms layer 14 | 16 | 18 | 19 | 20 | 21 | 22 | Fred Nurk 23 | Australian Nurk Organisation 24 | 25 | 26 | Developer 27 | 28 | None 29 |
Canberra
30 | Canberra 31 | ACT 32 | 2600 33 | Australia 34 |
35 | 0123456789 36 | fred.nurk@somewhere.com 37 | 38 |
39 | 40 | 41 | none 42 | none 43 | 20 44 | 2000 45 | 2000 46 |
47 | 48 | 49 | 50 | 51 | text/xml 52 | 53 | 54 | image/png 55 | 56 | 57 | 58 | application/vnd.ogc.gml 59 | text/xml 60 | text/html 61 | 62 | 63 | 64 | XML 65 | INIMAGE 66 | BLANK 67 | 68 | 69 | 70 | Darkness 71 | Darkness 72 | EPSG:4326 73 | EPSG:3857 74 | 75 | -180 76 | 180 77 | -90 78 | 90 79 | 80 | 82 | 500000000 83 | 84 | 85 |
86 | 87 | -------------------------------------------------------------------------------- /wms-demo/src/main/webapp/WEB-INF/classes/log4j.properties: -------------------------------------------------------------------------------- 1 | log4j.rootLogger= INFO, console 2 | log4j.appender.console=org.apache.log4j.ConsoleAppender 3 | log4j.appender.console.layout=org.apache.log4j.PatternLayout 4 | 5 | # Print the date in ISO 8601 format 6 | #log4j.appender.console.layout.ConversionPattern=%d [%t] %-5p %c - %m%n 7 | log4j.appender.console.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss.SSS} %-5p %c [%t] - %m%n 8 | 9 | # Print only messages of level WARN or above in the package com.foo. 10 | #log4j.logger.com.foo=WARN -------------------------------------------------------------------------------- /wms-demo/src/main/webapp/WEB-INF/web.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | ${project.artifactId} ${project.version} 6 | 7 | 8 | Wms 9 | com.github.davidmoten.grumpy.wms.demo.WmsServlet 10 | 11 | 12 | 13 | Wms 14 | /wms 15 | 16 | 17 | 18 | Map 19 | /map.jsp 20 | 21 | 22 | 23 | Map 24 | /map 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /wms-demo/src/main/webapp/css/style.css: -------------------------------------------------------------------------------- 1 | /** 2 | * CSS Reset 3 | * From Blueprint reset.css 4 | * http://blueprintcss.googlecode.com 5 | */ 6 | html, body, div, span, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, code, del, dfn, em, img, q, dl, dt, dd, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td {margin:0;padding:0;border:0;font-weight:inherit;font-style:inherit;font-size:100%;font-family:inherit;vertical-align:baseline;} 7 | body {line-height:1.5;} 8 | table {border-collapse:separate;border-spacing:0;} 9 | caption, th, td {text-align:left;font-weight:normal;} 10 | table, td, th {vertical-align:middle;} 11 | blockquote:before, blockquote:after, q:before, q:after {content:"";} 12 | blockquote, q {quotes:"" "";} 13 | a img {border:none;} 14 | 15 | /** 16 | * Basic Typography 17 | */ 18 | body { 19 | font-family: "Lucida Grande", Verdana, Geneva, Lucida, Arial, Helvetica, sans-serif; 20 | font-size: 80%; 21 | color: #222; 22 | background: #fff; 23 | margin: 1em 1.5em; 24 | } 25 | pre, code { 26 | margin: 1.5em 0; 27 | white-space: pre; 28 | } 29 | pre, code { 30 | font: 1em 'andale mono', 'lucida console', monospace; 31 | line-height:1.5; 32 | } 33 | a[href] { 34 | color: #436976; 35 | background-color: transparent; 36 | } 37 | h1, h2, h3, h4, h5, h6 { 38 | color: #003a6b; 39 | background-color: transparent; 40 | font: 100% 'Lucida Grande', Verdana, Geneva, Lucida, Arial, Helvetica, sans-serif; 41 | margin: 0; 42 | padding-top: 0.5em; 43 | } 44 | h1 { 45 | font-size: 130%; 46 | margin-bottom: 0.5em; 47 | border-bottom: 1px solid #fcb100; 48 | } 49 | h2 { 50 | font-size: 120%; 51 | margin-bottom: 0.5em; 52 | border-bottom: 1px solid #aaa; 53 | } 54 | h3 { 55 | font-size: 110%; 56 | margin-bottom: 0.5em; 57 | text-decoration: underline; 58 | } 59 | h4 { 60 | font-size: 100%; 61 | font-weight: bold; 62 | } 63 | h5 { 64 | font-size: 100%; 65 | font-weight: bold; 66 | } 67 | h6 { 68 | font-size: 80%; 69 | font-weight: bold; 70 | } 71 | 72 | .olControlAttribution { 73 | bottom: 5px; 74 | } 75 | 76 | /** 77 | * Map Examples Specific 78 | */ 79 | .fullmap { 80 | position:absolute; 81 | top:0; 82 | left:0; 83 | width:100%; 84 | height:100%; 85 | border: 1px solid #ccc; 86 | } 87 | #tags { 88 | display: none; 89 | } 90 | 91 | #docs p { 92 | margin-bottom: 0.5em; 93 | } 94 | /* mobile specific */ 95 | @media only screen and (max-width: 600px) { 96 | body { 97 | height : 100%; 98 | margin : 0; 99 | padding : 0; 100 | width : 100%; 101 | } 102 | #map { 103 | background : #7391ad; 104 | width : 100%; 105 | } 106 | #map { 107 | position: absolute; 108 | top: 0; 109 | left:0; 110 | border : 0; 111 | height : 100%; 112 | } 113 | #title { 114 | font-size : 1.3em; 115 | line-height : 2em; 116 | text-indent : 1em; 117 | margin : 0; 118 | padding : 0; 119 | } 120 | #docs { 121 | bottom : 0; 122 | padding : 1em; 123 | } 124 | #shortdesc { 125 | color : #aaa; 126 | font-size : 0.8em; 127 | padding : 1em; 128 | text-align : right; 129 | } 130 | #tags { 131 | display : none; 132 | } 133 | } 134 | @media only screen and (orientation: landscape) and (max-width: 600px) { 135 | #shortdesc { 136 | float: right; 137 | width: 25%; 138 | } 139 | #map { 140 | width: 70%; 141 | } 142 | #docs { 143 | font-size: 12px; 144 | } 145 | } 146 | body { 147 | -webkit-text-size-adjust: none; 148 | } 149 | -------------------------------------------------------------------------------- /wms-demo/src/main/webapp/index.html: -------------------------------------------------------------------------------- 1 | 2 |

WMS Demo

3 | 4 | 9 | 10 | -------------------------------------------------------------------------------- /wms-demo/src/main/webapp/js/base-layers-3857.js: -------------------------------------------------------------------------------- 1 | var map; 2 | 3 | function init() { 4 | 5 | /////////////////////////////////////////// 6 | // setup the base map with google layers 7 | /////////////////////////////////////////// 8 | 9 | map = new OpenLayers.Map('map', { 10 | projection: 'EPSG:3857', 11 | layers: [ 12 | new OpenLayers.Layer.OSM( "OpenStreetMap"), 13 | new OpenLayers.Layer.Google( 14 | "Google Physical", 15 | {type: google.maps.MapTypeId.TERRAIN} 16 | ), 17 | new OpenLayers.Layer.Google( 18 | "Google Streets", // the default 19 | {numZoomLevels: 20} 20 | ), 21 | new OpenLayers.Layer.Google( 22 | "Google Hybrid", 23 | {type: google.maps.MapTypeId.HYBRID, numZoomLevels: 20} 24 | ), 25 | new OpenLayers.Layer.Google( 26 | "Google Satellite", 27 | {type: google.maps.MapTypeId.SATELLITE, numZoomLevels: 22} 28 | ) 29 | ], 30 | center: new OpenLayers.LonLat(149.1,-35.3) 31 | // Google.v3 uses web mercator as projection, so we have to 32 | // transform our coordinates 33 | .transform('EPSG:4326', 'EPSG:3857'), 34 | zoom: 6, 35 | zoomMethod: null 36 | }); 37 | 38 | map.addControl(new OpenLayers.Control.LayerSwitcher()); 39 | 40 | addLayers(map); 41 | } 42 | 43 | -------------------------------------------------------------------------------- /wms-demo/src/main/webapp/js/base-layers-4326.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | function init(){ 4 | var lon = 140; 5 | var lat = -35; 6 | var zoom = 5; 7 | var map = new OpenLayers.Map('map'); 8 | var layer = new OpenLayers.Layer.WMS( "OpenLayers WMS", 9 | "http://vmap0.tiles.osgeo.org/wms/vmap0", {layers: 'basic'} ); 10 | map.addLayer(layer); 11 | map.setCenter(new OpenLayers.LonLat(lon, lat), zoom); 12 | addLayers(map); 13 | } -------------------------------------------------------------------------------- /wms-demo/src/main/webapp/js/layers.js: -------------------------------------------------------------------------------- 1 | 2 | function addLayers(map) { 3 | 4 | 5 | /////////////////////////////////////////// 6 | // setup the custom wms layer 7 | /////////////////////////////////////////// 8 | 9 | var wmsUrl = "wms"; 10 | 11 | var layer1 = new OpenLayers.Layer.WMS( "Custom WMS Layer", 12 | wmsUrl, 13 | {layers: 'Custom',transparent: "true", format: "image/png",styles:"Standard"}, 14 | {gutter:15,singleTile:true, visibility:true,opacity: 0.5,animationEnabled: false}); 15 | 16 | map.addLayer(layer1); 17 | 18 | /////////////////////////////////////////// 19 | // add the Darkness layer 20 | /////////////////////////////////////////// 21 | var layer2 = new OpenLayers.Layer.WMS( "Darkness", 22 | wmsUrl, 23 | {layers: 'Darkness',transparent: "true", format: "image/png",styles:"Standard"}, 24 | {gutter:15,singleTile:true, visibility:true,opacity: 0.5,animationEnabled: true}); 25 | 26 | map.addLayer(layer2); 27 | 28 | /////////////////////////////////////////// 29 | // add the Fiddle layer 30 | /////////////////////////////////////////// 31 | var layer3 = new OpenLayers.Layer.WMS( "Fiddle", 32 | wmsUrl, 33 | {layers: 'Fiddle',transparent: "true", format: "image/png"}, 34 | {gutter:15,singleTile:true, visibility:true,opacity: 0.5,animationEnabled: true}); 35 | 36 | map.addLayer(layer3); 37 | 38 | //////////////////////////////////////////////////// 39 | // setup getFeatureInfo on click for all layers 40 | ////////////////////////////////////////////////////s 41 | 42 | var click = new OpenLayers.Control.WMSGetFeatureInfo({ 43 | url: wmsUrl, 44 | title: 'Identify features by clicking', 45 | layers: [layer1], 46 | queryVisible: true 47 | }) 48 | click.events.register("getfeatureinfo", this, showInfo); 49 | map.addControl(click); 50 | click.activate(); 51 | 52 | } 53 | 54 | function showInfo(event) { 55 | map.addPopup(new OpenLayers.Popup.FramedCloud( 56 | "chicken", 57 | map.getLonLatFromPixel(event.xy), 58 | null, 59 | event.text, 60 | null, 61 | true 62 | )); 63 | }; 64 | -------------------------------------------------------------------------------- /wms-demo/src/main/webapp/map-3857.jsp: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | OpenLayers Google (v3) Layer Example 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | -------------------------------------------------------------------------------- /wms-demo/src/main/webapp/map-4326.jsp: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | OpenLayers EPSG:4326 Example 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | --------------------------------------------------------------------------------