Merge tag 'geocsv-0.1.1'
This commit is contained in:
		
						commit
						576db9e88f
					
				
							
								
								
									
										24
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										24
									
								
								README.md
									
									
									
									
									
								
							|  | @ -1,6 +1,6 @@ | |||
| # geocsv | ||||
| 
 | ||||
| GeoCSV is a wee tool to show comma-separated value data on a map. | ||||
| A wee tool to show comma-separated value data on a map. | ||||
| 
 | ||||
| The CSV file must have | ||||
| 
 | ||||
|  | @ -20,18 +20,32 @@ If you run the server running **geocsv**, the simplest way to add CSV files is s | |||
| 
 | ||||
|     https://geocsv.example.com/ | ||||
| 
 | ||||
| and the file you want to view is `myfile.csv`, then you would specify this as | ||||
| and the file you want to view is `myfile.csv`, then you would specify this as the value of `file` in the query part of the URL. | ||||
| 
 | ||||
|     https://geocsv.example.com/?file=myfile.csv | ||||
| 
 | ||||
| ### Loading CSV file onto another public server | ||||
| 
 | ||||
| If you're not running the **geocsv** server yourself, you can upload the CSV to another server which is accessible by the **geocsv** server. You can then map data from the CSV file by specifying the URL of the file as the value of `uri` in the query part of the URL: | ||||
| 
 | ||||
|     https://geocsv.example.com/?uri=http://my.other.server/path/to/myfile.csv | ||||
| 
 | ||||
| ### Using a Google spreadsheet | ||||
| 
 | ||||
| If you use [Google Sheets](https://www.google.co.uk/sheets/about/), then every sheet has a 'document id', a long string of characters which uniquely identifies that sheet. Suppose your Google spreadsheet has a document id of `abcdefghijklmnopqrstuvwxyz-12345`, then you could pull data from this spreadsheet by specifying: | ||||
| If you use [Google Sheets](https://www.google.co.uk/sheets/about/), then every sheet has a 'document id', a long string of characters which uniquely identifies that sheet. Suppose your Google spreadsheet has a document id of `abcdefghijklmnopqrstuvwxyz-12345`, then you could pull data from this spreadsheet by specifying this as the value of `docid` in the query part of the URL: | ||||
| 
 | ||||
|     https://geocsv.example.com/?docid=abcdefghijklmnopqrstuvwxyz-12345 | ||||
| 
 | ||||
| The spreadsheet **must** be publicly readable. | ||||
| 
 | ||||
| ### Precedence | ||||
| 
 | ||||
| Nothing, of course, stops you from specifying multiple arguments in the query part of the URL, but only one will be used. The precedence is in this order: | ||||
| 
 | ||||
| 1. `docid` is considered first, and overrides anything else; | ||||
| 2. `uri` is considered next, and overrides `file`; | ||||
| 3. the value of `file` is considered only if neither of the other two are present. | ||||
| 
 | ||||
| ## Not yet working | ||||
| 
 | ||||
| GeoCSV is at an early stage of development, and some features are not yet working. | ||||
|  | @ -40,10 +54,6 @@ GeoCSV is at an early stage of development, and some features are not yet workin | |||
| 
 | ||||
| At the current stage of development, if no appropriate image exists in the `resources/public/img/map-pins` folder, that's your problem. **TODO:** I intend at some point to make missing pin images default to `unknown-pin.png`, which does exist. | ||||
| 
 | ||||
| ### Doesn't scale and centre the map to show the data in the sheet | ||||
| 
 | ||||
| Currently the map is initially centred roughly on the centre of Scotland, and scaled arbitrarily. It should compute an appropriate centre and scale from the data provided, but currently doesn't. | ||||
| 
 | ||||
| ## Prerequisites | ||||
| 
 | ||||
| You will need [Leiningen][1] 2.0 or above installed. | ||||
|  |  | |||
							
								
								
									
										2
									
								
								env/dev/cljs/geocsv/app.cljs
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								env/dev/cljs/geocsv/app.cljs
									
									
									
									
										vendored
									
									
								
							|  | @ -1,6 +1,6 @@ | |||
| (ns^:figwheel-no-load geocsv.app | ||||
|   (:require | ||||
|     [geocsv.core :as core] | ||||
|     [geocsv.client.core :as core] | ||||
|     [cljs.spec.alpha :as s] | ||||
|     [expound.alpha :as expound] | ||||
|     [devtools.core :as devtools])) | ||||
|  |  | |||
							
								
								
									
										2
									
								
								env/prod/cljs/geocsv/app.cljs
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								env/prod/cljs/geocsv/app.cljs
									
									
									
									
										vendored
									
									
								
							|  | @ -1,5 +1,5 @@ | |||
| (ns geocsv.app | ||||
|   (:require [geocsv.core :as core])) | ||||
|   (:require [geocsv.client.core :as core])) | ||||
| 
 | ||||
| ;;ignore println statements in prod | ||||
| (set! *print-fn* (fn [& _])) | ||||
|  |  | |||
							
								
								
									
										13
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										13
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							|  | @ -1,13 +0,0 @@ | |||
| { | ||||
|   "name": "geocsv", | ||||
|   "version": "0.1.0-SNAPSHOT", | ||||
|   "lockfileVersion": 1, | ||||
|   "requires": true, | ||||
|   "dependencies": { | ||||
|     "leaflet": { | ||||
|       "version": "1.3.1", | ||||
|       "resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.3.1.tgz", | ||||
|       "integrity": "sha512-adQOIzh+bfdridLM1xIgJ9VnJbAUY3wqs/ueF+ITla+PLQ1z47USdBKUf+iD9FuUA8RtlT6j6hZBfZoA6mW+XQ==" | ||||
|     } | ||||
|   } | ||||
| } | ||||
							
								
								
									
										28
									
								
								project.clj
									
									
									
									
									
								
							
							
						
						
									
										28
									
								
								project.clj
									
									
									
									
									
								
							|  | @ -1,4 +1,4 @@ | |||
| (defproject geocsv "0.1.0" | ||||
| (defproject geocsv "0.1.1" | ||||
| 
 | ||||
|   :description "A wee tool to show comma-separated value data on a map." | ||||
|   :url "http://example.com/FIXME" | ||||
|  | @ -12,8 +12,9 @@ | |||
|                  [com.cemerick/url "0.1.1"] | ||||
|                  [com.cognitect/transit-clj "0.8.319"] | ||||
|                  [compojure "1.6.1"] | ||||
|                  [cpath-clj "0.1.2"] | ||||
|                  [cprop "0.1.15"] | ||||
|                  [csv2edn "0.1.5"] | ||||
|                  [csv2edn "0.1.6"] | ||||
|                  [day8.re-frame/http-fx "0.1.6"] | ||||
|                  [expound "0.8.3"] | ||||
|                  [funcool/struct "1.4.0"] | ||||
|  | @ -32,6 +33,7 @@ | |||
|                  [org.clojure/tools.cli "0.4.2"] | ||||
|                  [org.clojure/tools.logging "0.5.0"] | ||||
|                  [org.webjars.npm/bulma "0.8.0"] | ||||
|                  [org.webjars.npm/leaflet "1.6.0"] | ||||
|                  [org.webjars.npm/material-icons "0.3.1"] | ||||
|                  [org.webjars/webjars-locator "0.38"] | ||||
|                  [re-frame "0.10.9"] | ||||
|  | @ -43,7 +45,6 @@ | |||
|                  [selmer "1.12.18"]] | ||||
| 
 | ||||
|   :min-lein-version "2.0.0" | ||||
|   :npm {:dependencies [[leaflet "1.3.1"]]} | ||||
| 
 | ||||
|   :source-paths ["src/clj" "src/cljs" "src/cljc"] | ||||
|   :test-paths ["test/clj"] | ||||
|  | @ -53,9 +54,11 @@ | |||
| 
 | ||||
|   :plugins [[lein-cljsbuild "1.1.7"] | ||||
|             [lein-codox "0.10.7"] | ||||
|             [lein-npm "0.6.2"] | ||||
|             [lein-release "1.0.5"]] | ||||
| 
 | ||||
|   :deploy-repositories [["releases" :clojars] | ||||
|                         ["snapshots" :clojars]] | ||||
| 
 | ||||
|   :clean-targets ^{:protect false} | ||||
|   [:target-path [:cljsbuild :builds :app :compiler :output-dir] [:cljsbuild :builds :app :compiler :output-to]] | ||||
|   :figwheel | ||||
|  | @ -108,7 +111,7 @@ | |||
|                   :cljsbuild{:builds | ||||
|                    {:app | ||||
|                     {:source-paths ["src/cljs" "src/cljc" "env/dev/cljs"] | ||||
|                      :figwheel {:on-jsload "geocsv.core/mount-components"} | ||||
|                      :figwheel {:on-jsload "geocsv.client.core/mount-components"} | ||||
|                      :compiler | ||||
|                      {:output-dir "target/cljsbuild/public/js/out" | ||||
|                       :closure-defines {"re_frame.trace.trace_enabled_QMARK_" true} | ||||
|  | @ -141,4 +144,17 @@ | |||
| 
 | ||||
|                   } | ||||
|    :profiles/dev {} | ||||
|    :profiles/test {}}) | ||||
|    :profiles/test {}} | ||||
| 
 | ||||
|   ;; `lein release` doesn't play nice with `git flow release`. Run `lein release` in the | ||||
|   ;; `develop` branch, then reset the `master` branch to the release tag. | ||||
| 
 | ||||
|   :release-tasks [["vcs" "assert-committed"] | ||||
|                   ["clean"] | ||||
|                   ["codox"] | ||||
|                   ["change" "version" "leiningen.release/bump-version" "release"] | ||||
|                   ["vcs" "commit"] | ||||
|                   ["uberjar"] | ||||
|                   ["deploy" "clojars"] | ||||
|                   ["change" "version" "leiningen.release/bump-version"] | ||||
|                   ["vcs" "commit"]]) | ||||
|  |  | |||
|  | @ -1,6 +1,6 @@ | |||
| # geocsv | ||||
| 
 | ||||
| GeoCSV is a wee tool to show comma-separated value data on a map. | ||||
| A wee tool to show comma-separated value data on a map. | ||||
| 
 | ||||
| The CSV file must have | ||||
| 
 | ||||
|  | @ -12,42 +12,48 @@ The CSV file must have | |||
| 
 | ||||
| Additionally, the value of the column `category`, if present, will be used to select map pins from the map pins folder, if a suitable pin is present. Thus is the value of `category` is `foo`, a map pin image with the name `Foo-pin.png` will be selected. | ||||
| 
 | ||||
| ## Passing CSV files to the app | ||||
| 
 | ||||
| ### Loading them onto the server | ||||
| 
 | ||||
| If you run the server running **geocsv**, the simplest way to add CSV files is simply to copy them into the directory `resourcs/data`. The default file is the one named `data.csv`, which is the one that will be served if nothing else is specified. Other files can be specifiec by appending `?file=filename` to the URL; so if the URL of your geocsv service is | ||||
| 
 | ||||
|     https://geocsv.example.com/ | ||||
| 
 | ||||
| and the file you want to view is `myfile.csv`, then you would specify this as the value of `file` in the query part of the URL. | ||||
| 
 | ||||
|     https://geocsv.example.com/?file=myfile.csv | ||||
| 
 | ||||
| ### Loading CSV file onto another public server | ||||
| 
 | ||||
| If you're not running the **geocsv** server yourself, you can upload the CSV to another server which is accessible by the **geocsv** server. You can then map data from the CSV file by specifying the URL of the file as the value of `uri` in the query part of the URL: | ||||
| 
 | ||||
|     https://geocsv.example.com/?uri=http://my.other.server/path/to/myfile.csv | ||||
| 
 | ||||
| ### Using a Google spreadsheet | ||||
| 
 | ||||
| If you use [Google Sheets](https://www.google.co.uk/sheets/about/), then every sheet has a 'document id', a long string of characters which uniquely identifies that sheet. Suppose your Google spreadsheet has a document id of `abcdefghijklmnopqrstuvwxyz-12345`, then you could pull data from this spreadsheet by specifying this as the value of `docid` in the query part of the URL: | ||||
| 
 | ||||
|     https://geocsv.example.com/?docid=abcdefghijklmnopqrstuvwxyz-12345 | ||||
| 
 | ||||
| The spreadsheet **must** be publicly readable. | ||||
| 
 | ||||
| ### Precedence | ||||
| 
 | ||||
| Nothing, of course, stops you from specifying multiple arguments in the query part of the URL, but only one will be used. The precedence is in this order: | ||||
| 
 | ||||
| 1. `docid` is considered first, and overrides anything else; | ||||
| 2. `uri` is considered next, and overrides `file`; | ||||
| 3. the value of `file` is considered only if neither of the other two are present. | ||||
| 
 | ||||
| ## Not yet working | ||||
| 
 | ||||
| GeoCSV is at an early stage of development, and some features are not yet working. | ||||
| 
 | ||||
| ### Doesn't actually interpret CSV | ||||
| 
 | ||||
| I haven't yet found an easy way to parse CSV into EDN client side, so I've written a [separate library](https://github.com/simon-brooke/csv2edn) to do it server side. However, that library is not yet integrated. Currently the client side actually interprets JSON. | ||||
| 
 | ||||
| ### Missing map pin images | ||||
| 
 | ||||
| At the current stage of development, if no appropriate image exists in the `resources/public/img/map-pins` folder, that's your problem. **TODO:** I intend at some point to make missing pin images default to `unknown-pin.png`, which does exist. | ||||
| 
 | ||||
| ### Doesn't scale and centre the map to show the data in the sheet | ||||
| 
 | ||||
| Currently the map is initially centred roughly on the centre of Scotland, and scaled arbitrarily. It should compute an appropriate centre and scale from the data provided, but currently doesn't. | ||||
| 
 | ||||
| ### There's no way of linking your own data feed | ||||
| 
 | ||||
| Currently, the data is taken from the file `resources/public/data/data.json`. What I intend is that you should have a form which allows you to either | ||||
| 
 | ||||
| 1. enter [the `DOCID` of your own (publicly readable) Google Sheets spreadsheet](https://stackoverflow.com/questions/33713084/download-link-for-google-spreadsheets-csv-export-with-multiple-sheets); | ||||
| 2. enter the URL of a CSV file publicly available on the web; | ||||
| 3. upload a CSV file to the server. | ||||
| 
 | ||||
| ### There's no way of shareing the map of your own data with other people | ||||
| 
 | ||||
| Currently, the data that is shared is just the data that's present when the app is compiled. Ideally, there should be a way of generating a URL, which might take the form: | ||||
| 
 | ||||
|     https://server.name/geocsv/docid/564747867 | ||||
| 
 | ||||
| To show data from the first sheet of the Google Sheets spreadsheet whose `DOCID` is 564747867; or | ||||
| 
 | ||||
|     https://server.name/geocsv?uri=https://address.of.another.server/path/to/csv-file.csv | ||||
| 
 | ||||
| to show the content of a publicly available CSV file. | ||||
| 
 | ||||
| ## Prerequisites | ||||
| 
 | ||||
| You will need [Leiningen][1] 2.0 or above installed. | ||||
|  |  | |||
|  | @ -6,27 +6,26 @@ | |||
|       <title>Welcome to geocsv</title> | ||||
|   </head> | ||||
|   <body> | ||||
| 
 | ||||
|     <div id="app"> | ||||
|       <div class="splash-screen"> | ||||
|         <div class="sk-fading-circle"> | ||||
|           <div class="sk-circle1 sk-circle"></div> | ||||
|           <div class="sk-circle2 sk-circle"></div> | ||||
|           <div class="sk-circle3 sk-circle"></div> | ||||
|           <div class="sk-circle4 sk-circle"></div> | ||||
|           <div class="sk-circle5 sk-circle"></div> | ||||
|           <div class="sk-circle6 sk-circle"></div> | ||||
|           <div class="sk-circle7 sk-circle"></div> | ||||
|           <div class="sk-circle8 sk-circle"></div> | ||||
|           <div class="sk-circle9 sk-circle"></div> | ||||
|           <div class="sk-circle10 sk-circle"></div> | ||||
|           <div class="sk-circle11 sk-circle"></div> | ||||
|           <div class="sk-circle12 sk-circle"></div> | ||||
|       <section class="section"> | ||||
|         <div class="container is-fluid"> | ||||
|           <div class="content"> | ||||
|             <h4 class="title">Welcome to geocsv</h4> | ||||
|             <p>If you're seeing this message, that means you haven't yet compiled your ClojureScript!</p> | ||||
|             <p>Please run <code>lein figwheel</code> to start the ClojureScript compiler and reload the page.</p> | ||||
|             <h4>For better ClojureScript development experience in Chrome follow these steps:</h4> | ||||
|             <ul> | ||||
|               <li>Open DevTools | ||||
|               <li>Go to Settings ("three dots" icon in the upper right corner of DevTools > Menu > Settings F1 > General > Console) | ||||
|               <li>Check-in "Enable custom formatters" | ||||
|               <li>Close DevTools | ||||
|               <li>Open DevTools | ||||
|             </ul> | ||||
|             <p>See <a href="http://www.luminusweb.net/docs/clojurescript.md">ClojureScript</a> documentation for further details.</p> | ||||
|           </div> | ||||
|         </div> | ||||
|       <p class="footer"> | ||||
|         <b>geocsv</b> is loading. | ||||
|         You must enable JavaScript to use <b>geocsv</b>. | ||||
|       </p> | ||||
|       </section> | ||||
|     </div> | ||||
| 
 | ||||
|     {% block foot %} | ||||
|  | @ -56,8 +55,8 @@ | |||
|     <!-- ATTENTION \/ --> | ||||
|     <!-- ATTENTION /\ --> | ||||
|     <!-- Leaflet --> | ||||
|     {% style "js/lib/node_modules/leaflet/dist/leaflet.css" %} | ||||
|     {% script "/js/lib/node_modules/leaflet/dist/leaflet.js" %} | ||||
|     {% style "/assets/leaflet/dist/leaflet.css" %} | ||||
|     {% script "/assets/leaflet/dist/leaflet.js" %} | ||||
|     {% script "/js/app.js" %} | ||||
|   </body> | ||||
| </html> | ||||
|  |  | |||
| Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB | 
| Before Width: | Height: | Size: 5.1 KiB After Width: | Height: | Size: 5.1 KiB | 
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										636
									
								
								resources/public/js/lib/node_modules/leaflet/dist/leaflet.css
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										636
									
								
								resources/public/js/lib/node_modules/leaflet/dist/leaflet.css
									
									
									
										generated
									
									
										vendored
									
									
								
							|  | @ -1,636 +0,0 @@ | |||
| /* required styles */ | ||||
| 
 | ||||
| .leaflet-pane, | ||||
| .leaflet-tile, | ||||
| .leaflet-marker-icon, | ||||
| .leaflet-marker-shadow, | ||||
| .leaflet-tile-container, | ||||
| .leaflet-pane > svg, | ||||
| .leaflet-pane > canvas, | ||||
| .leaflet-zoom-box, | ||||
| .leaflet-image-layer, | ||||
| .leaflet-layer { | ||||
| 	position: absolute; | ||||
| 	left: 0; | ||||
| 	top: 0; | ||||
| 	} | ||||
| .leaflet-container { | ||||
| 	overflow: hidden; | ||||
| 	} | ||||
| .leaflet-tile, | ||||
| .leaflet-marker-icon, | ||||
| .leaflet-marker-shadow { | ||||
| 	-webkit-user-select: none; | ||||
| 	   -moz-user-select: none; | ||||
| 	        user-select: none; | ||||
| 	  -webkit-user-drag: none; | ||||
| 	} | ||||
| /* Safari renders non-retina tile on retina better with this, but Chrome is worse */ | ||||
| .leaflet-safari .leaflet-tile { | ||||
| 	image-rendering: -webkit-optimize-contrast; | ||||
| 	} | ||||
| /* hack that prevents hw layers "stretching" when loading new tiles */ | ||||
| .leaflet-safari .leaflet-tile-container { | ||||
| 	width: 1600px; | ||||
| 	height: 1600px; | ||||
| 	-webkit-transform-origin: 0 0; | ||||
| 	} | ||||
| .leaflet-marker-icon, | ||||
| .leaflet-marker-shadow { | ||||
| 	display: block; | ||||
| 	} | ||||
| /* .leaflet-container svg: reset svg max-width decleration shipped in Joomla! (joomla.org) 3.x */ | ||||
| /* .leaflet-container img: map is broken in FF if you have max-width: 100% on tiles */ | ||||
| .leaflet-container .leaflet-overlay-pane svg, | ||||
| .leaflet-container .leaflet-marker-pane img, | ||||
| .leaflet-container .leaflet-shadow-pane img, | ||||
| .leaflet-container .leaflet-tile-pane img, | ||||
| .leaflet-container img.leaflet-image-layer { | ||||
| 	max-width: none !important; | ||||
| 	max-height: none !important; | ||||
| 	} | ||||
| 
 | ||||
| .leaflet-container.leaflet-touch-zoom { | ||||
| 	-ms-touch-action: pan-x pan-y; | ||||
| 	touch-action: pan-x pan-y; | ||||
| 	} | ||||
| .leaflet-container.leaflet-touch-drag { | ||||
| 	-ms-touch-action: pinch-zoom; | ||||
| 	/* Fallback for FF which doesn't support pinch-zoom */ | ||||
| 	touch-action: none; | ||||
| 	touch-action: pinch-zoom; | ||||
| } | ||||
| .leaflet-container.leaflet-touch-drag.leaflet-touch-zoom { | ||||
| 	-ms-touch-action: none; | ||||
| 	touch-action: none; | ||||
| } | ||||
| .leaflet-container { | ||||
| 	-webkit-tap-highlight-color: transparent; | ||||
| } | ||||
| .leaflet-container a { | ||||
| 	-webkit-tap-highlight-color: rgba(51, 181, 229, 0.4); | ||||
| } | ||||
| .leaflet-tile { | ||||
| 	filter: inherit; | ||||
| 	visibility: hidden; | ||||
| 	} | ||||
| .leaflet-tile-loaded { | ||||
| 	visibility: inherit; | ||||
| 	} | ||||
| .leaflet-zoom-box { | ||||
| 	width: 0; | ||||
| 	height: 0; | ||||
| 	-moz-box-sizing: border-box; | ||||
| 	     box-sizing: border-box; | ||||
| 	z-index: 800; | ||||
| 	} | ||||
| /* workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=888319 */ | ||||
| .leaflet-overlay-pane svg { | ||||
| 	-moz-user-select: none; | ||||
| 	} | ||||
| 
 | ||||
| .leaflet-pane         { z-index: 400; } | ||||
| 
 | ||||
| .leaflet-tile-pane    { z-index: 200; } | ||||
| .leaflet-overlay-pane { z-index: 400; } | ||||
| .leaflet-shadow-pane  { z-index: 500; } | ||||
| .leaflet-marker-pane  { z-index: 600; } | ||||
| .leaflet-tooltip-pane   { z-index: 650; } | ||||
| .leaflet-popup-pane   { z-index: 700; } | ||||
| 
 | ||||
| .leaflet-map-pane canvas { z-index: 100; } | ||||
| .leaflet-map-pane svg    { z-index: 200; } | ||||
| 
 | ||||
| .leaflet-vml-shape { | ||||
| 	width: 1px; | ||||
| 	height: 1px; | ||||
| 	} | ||||
| .lvml { | ||||
| 	behavior: url(#default#VML); | ||||
| 	display: inline-block; | ||||
| 	position: absolute; | ||||
| 	} | ||||
| 
 | ||||
| 
 | ||||
| /* control positioning */ | ||||
| 
 | ||||
| .leaflet-control { | ||||
| 	position: relative; | ||||
| 	z-index: 800; | ||||
| 	pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */ | ||||
| 	pointer-events: auto; | ||||
| 	} | ||||
| .leaflet-top, | ||||
| .leaflet-bottom { | ||||
| 	position: absolute; | ||||
| 	z-index: 1000; | ||||
| 	pointer-events: none; | ||||
| 	} | ||||
| .leaflet-top { | ||||
| 	top: 0; | ||||
| 	} | ||||
| .leaflet-right { | ||||
| 	right: 0; | ||||
| 	} | ||||
| .leaflet-bottom { | ||||
| 	bottom: 0; | ||||
| 	} | ||||
| .leaflet-left { | ||||
| 	left: 0; | ||||
| 	} | ||||
| .leaflet-control { | ||||
| 	float: left; | ||||
| 	clear: both; | ||||
| 	} | ||||
| .leaflet-right .leaflet-control { | ||||
| 	float: right; | ||||
| 	} | ||||
| .leaflet-top .leaflet-control { | ||||
| 	margin-top: 10px; | ||||
| 	} | ||||
| .leaflet-bottom .leaflet-control { | ||||
| 	margin-bottom: 10px; | ||||
| 	} | ||||
| .leaflet-left .leaflet-control { | ||||
| 	margin-left: 10px; | ||||
| 	} | ||||
| .leaflet-right .leaflet-control { | ||||
| 	margin-right: 10px; | ||||
| 	} | ||||
| 
 | ||||
| 
 | ||||
| /* zoom and fade animations */ | ||||
| 
 | ||||
| .leaflet-fade-anim .leaflet-tile { | ||||
| 	will-change: opacity; | ||||
| 	} | ||||
| .leaflet-fade-anim .leaflet-popup { | ||||
| 	opacity: 0; | ||||
| 	-webkit-transition: opacity 0.2s linear; | ||||
| 	   -moz-transition: opacity 0.2s linear; | ||||
| 	     -o-transition: opacity 0.2s linear; | ||||
| 	        transition: opacity 0.2s linear; | ||||
| 	} | ||||
| .leaflet-fade-anim .leaflet-map-pane .leaflet-popup { | ||||
| 	opacity: 1; | ||||
| 	} | ||||
| .leaflet-zoom-animated { | ||||
| 	-webkit-transform-origin: 0 0; | ||||
| 	    -ms-transform-origin: 0 0; | ||||
| 	        transform-origin: 0 0; | ||||
| 	} | ||||
| .leaflet-zoom-anim .leaflet-zoom-animated { | ||||
| 	will-change: transform; | ||||
| 	} | ||||
| .leaflet-zoom-anim .leaflet-zoom-animated { | ||||
| 	-webkit-transition: -webkit-transform 0.25s cubic-bezier(0,0,0.25,1); | ||||
| 	   -moz-transition:    -moz-transform 0.25s cubic-bezier(0,0,0.25,1); | ||||
| 	     -o-transition:      -o-transform 0.25s cubic-bezier(0,0,0.25,1); | ||||
| 	        transition:         transform 0.25s cubic-bezier(0,0,0.25,1); | ||||
| 	} | ||||
| .leaflet-zoom-anim .leaflet-tile, | ||||
| .leaflet-pan-anim .leaflet-tile { | ||||
| 	-webkit-transition: none; | ||||
| 	   -moz-transition: none; | ||||
| 	     -o-transition: none; | ||||
| 	        transition: none; | ||||
| 	} | ||||
| 
 | ||||
| .leaflet-zoom-anim .leaflet-zoom-hide { | ||||
| 	visibility: hidden; | ||||
| 	} | ||||
| 
 | ||||
| 
 | ||||
| /* cursors */ | ||||
| 
 | ||||
| .leaflet-interactive { | ||||
| 	cursor: pointer; | ||||
| 	} | ||||
| .leaflet-grab { | ||||
| 	cursor: -webkit-grab; | ||||
| 	cursor:    -moz-grab; | ||||
| 	} | ||||
| .leaflet-crosshair, | ||||
| .leaflet-crosshair .leaflet-interactive { | ||||
| 	cursor: crosshair; | ||||
| 	} | ||||
| .leaflet-popup-pane, | ||||
| .leaflet-control { | ||||
| 	cursor: auto; | ||||
| 	} | ||||
| .leaflet-dragging .leaflet-grab, | ||||
| .leaflet-dragging .leaflet-grab .leaflet-interactive, | ||||
| .leaflet-dragging .leaflet-marker-draggable { | ||||
| 	cursor: move; | ||||
| 	cursor: -webkit-grabbing; | ||||
| 	cursor:    -moz-grabbing; | ||||
| 	} | ||||
| 
 | ||||
| /* marker & overlays interactivity */ | ||||
| .leaflet-marker-icon, | ||||
| .leaflet-marker-shadow, | ||||
| .leaflet-image-layer, | ||||
| .leaflet-pane > svg path, | ||||
| .leaflet-tile-container { | ||||
| 	pointer-events: none; | ||||
| 	} | ||||
| 
 | ||||
| .leaflet-marker-icon.leaflet-interactive, | ||||
| .leaflet-image-layer.leaflet-interactive, | ||||
| .leaflet-pane > svg path.leaflet-interactive { | ||||
| 	pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */ | ||||
| 	pointer-events: auto; | ||||
| 	} | ||||
| 
 | ||||
| /* visual tweaks */ | ||||
| 
 | ||||
| .leaflet-container { | ||||
| 	background: #ddd; | ||||
| 	outline: 0; | ||||
| 	} | ||||
| .leaflet-container a { | ||||
| 	color: #0078A8; | ||||
| 	} | ||||
| .leaflet-container a.leaflet-active { | ||||
| 	outline: 2px solid orange; | ||||
| 	} | ||||
| .leaflet-zoom-box { | ||||
| 	border: 2px dotted #38f; | ||||
| 	background: rgba(255,255,255,0.5); | ||||
| 	} | ||||
| 
 | ||||
| 
 | ||||
| /* general typography */ | ||||
| .leaflet-container { | ||||
| 	font: 12px/1.5 "Helvetica Neue", Arial, Helvetica, sans-serif; | ||||
| 	} | ||||
| 
 | ||||
| 
 | ||||
| /* general toolbar styles */ | ||||
| 
 | ||||
| .leaflet-bar { | ||||
| 	box-shadow: 0 1px 5px rgba(0,0,0,0.65); | ||||
| 	border-radius: 4px; | ||||
| 	} | ||||
| .leaflet-bar a, | ||||
| .leaflet-bar a:hover { | ||||
| 	background-color: #fff; | ||||
| 	border-bottom: 1px solid #ccc; | ||||
| 	width: 26px; | ||||
| 	height: 26px; | ||||
| 	line-height: 26px; | ||||
| 	display: block; | ||||
| 	text-align: center; | ||||
| 	text-decoration: none; | ||||
| 	color: black; | ||||
| 	} | ||||
| .leaflet-bar a, | ||||
| .leaflet-control-layers-toggle { | ||||
| 	background-position: 50% 50%; | ||||
| 	background-repeat: no-repeat; | ||||
| 	display: block; | ||||
| 	} | ||||
| .leaflet-bar a:hover { | ||||
| 	background-color: #f4f4f4; | ||||
| 	} | ||||
| .leaflet-bar a:first-child { | ||||
| 	border-top-left-radius: 4px; | ||||
| 	border-top-right-radius: 4px; | ||||
| 	} | ||||
| .leaflet-bar a:last-child { | ||||
| 	border-bottom-left-radius: 4px; | ||||
| 	border-bottom-right-radius: 4px; | ||||
| 	border-bottom: none; | ||||
| 	} | ||||
| .leaflet-bar a.leaflet-disabled { | ||||
| 	cursor: default; | ||||
| 	background-color: #f4f4f4; | ||||
| 	color: #bbb; | ||||
| 	} | ||||
| 
 | ||||
| .leaflet-touch .leaflet-bar a { | ||||
| 	width: 30px; | ||||
| 	height: 30px; | ||||
| 	line-height: 30px; | ||||
| 	} | ||||
| .leaflet-touch .leaflet-bar a:first-child { | ||||
| 	border-top-left-radius: 2px; | ||||
| 	border-top-right-radius: 2px; | ||||
| 	} | ||||
| .leaflet-touch .leaflet-bar a:last-child { | ||||
| 	border-bottom-left-radius: 2px; | ||||
| 	border-bottom-right-radius: 2px; | ||||
| 	} | ||||
| 
 | ||||
| /* zoom control */ | ||||
| 
 | ||||
| .leaflet-control-zoom-in, | ||||
| .leaflet-control-zoom-out { | ||||
| 	font: bold 18px 'Lucida Console', Monaco, monospace; | ||||
| 	text-indent: 1px; | ||||
| 	} | ||||
| 
 | ||||
| .leaflet-touch .leaflet-control-zoom-in, .leaflet-touch .leaflet-control-zoom-out  { | ||||
| 	font-size: 22px; | ||||
| 	} | ||||
| 
 | ||||
| 
 | ||||
| /* layers control */ | ||||
| 
 | ||||
| .leaflet-control-layers { | ||||
| 	box-shadow: 0 1px 5px rgba(0,0,0,0.4); | ||||
| 	background: #fff; | ||||
| 	border-radius: 5px; | ||||
| 	} | ||||
| .leaflet-control-layers-toggle { | ||||
| 	background-image: url(images/layers.png); | ||||
| 	width: 36px; | ||||
| 	height: 36px; | ||||
| 	} | ||||
| .leaflet-retina .leaflet-control-layers-toggle { | ||||
| 	background-image: url(images/layers-2x.png); | ||||
| 	background-size: 26px 26px; | ||||
| 	} | ||||
| .leaflet-touch .leaflet-control-layers-toggle { | ||||
| 	width: 44px; | ||||
| 	height: 44px; | ||||
| 	} | ||||
| .leaflet-control-layers .leaflet-control-layers-list, | ||||
| .leaflet-control-layers-expanded .leaflet-control-layers-toggle { | ||||
| 	display: none; | ||||
| 	} | ||||
| .leaflet-control-layers-expanded .leaflet-control-layers-list { | ||||
| 	display: block; | ||||
| 	position: relative; | ||||
| 	} | ||||
| .leaflet-control-layers-expanded { | ||||
| 	padding: 6px 10px 6px 6px; | ||||
| 	color: #333; | ||||
| 	background: #fff; | ||||
| 	} | ||||
| .leaflet-control-layers-scrollbar { | ||||
| 	overflow-y: scroll; | ||||
| 	overflow-x: hidden; | ||||
| 	padding-right: 5px; | ||||
| 	} | ||||
| .leaflet-control-layers-selector { | ||||
| 	margin-top: 2px; | ||||
| 	position: relative; | ||||
| 	top: 1px; | ||||
| 	} | ||||
| .leaflet-control-layers label { | ||||
| 	display: block; | ||||
| 	} | ||||
| .leaflet-control-layers-separator { | ||||
| 	height: 0; | ||||
| 	border-top: 1px solid #ddd; | ||||
| 	margin: 5px -10px 5px -6px; | ||||
| 	} | ||||
| 
 | ||||
| /* Default icon URLs */ | ||||
| .leaflet-default-icon-path { | ||||
| 	background-image: url(images/marker-icon.png); | ||||
| 	} | ||||
| 
 | ||||
| 
 | ||||
| /* attribution and scale controls */ | ||||
| 
 | ||||
| .leaflet-container .leaflet-control-attribution { | ||||
| 	background: #fff; | ||||
| 	background: rgba(255, 255, 255, 0.7); | ||||
| 	margin: 0; | ||||
| 	} | ||||
| .leaflet-control-attribution, | ||||
| .leaflet-control-scale-line { | ||||
| 	padding: 0 5px; | ||||
| 	color: #333; | ||||
| 	} | ||||
| .leaflet-control-attribution a { | ||||
| 	text-decoration: none; | ||||
| 	} | ||||
| .leaflet-control-attribution a:hover { | ||||
| 	text-decoration: underline; | ||||
| 	} | ||||
| .leaflet-container .leaflet-control-attribution, | ||||
| .leaflet-container .leaflet-control-scale { | ||||
| 	font-size: 11px; | ||||
| 	} | ||||
| .leaflet-left .leaflet-control-scale { | ||||
| 	margin-left: 5px; | ||||
| 	} | ||||
| .leaflet-bottom .leaflet-control-scale { | ||||
| 	margin-bottom: 5px; | ||||
| 	} | ||||
| .leaflet-control-scale-line { | ||||
| 	border: 2px solid #777; | ||||
| 	border-top: none; | ||||
| 	line-height: 1.1; | ||||
| 	padding: 2px 5px 1px; | ||||
| 	font-size: 11px; | ||||
| 	white-space: nowrap; | ||||
| 	overflow: hidden; | ||||
| 	-moz-box-sizing: border-box; | ||||
| 	     box-sizing: border-box; | ||||
| 
 | ||||
| 	background: #fff; | ||||
| 	background: rgba(255, 255, 255, 0.5); | ||||
| 	} | ||||
| .leaflet-control-scale-line:not(:first-child) { | ||||
| 	border-top: 2px solid #777; | ||||
| 	border-bottom: none; | ||||
| 	margin-top: -2px; | ||||
| 	} | ||||
| .leaflet-control-scale-line:not(:first-child):not(:last-child) { | ||||
| 	border-bottom: 2px solid #777; | ||||
| 	} | ||||
| 
 | ||||
| .leaflet-touch .leaflet-control-attribution, | ||||
| .leaflet-touch .leaflet-control-layers, | ||||
| .leaflet-touch .leaflet-bar { | ||||
| 	box-shadow: none; | ||||
| 	} | ||||
| .leaflet-touch .leaflet-control-layers, | ||||
| .leaflet-touch .leaflet-bar { | ||||
| 	border: 2px solid rgba(0,0,0,0.2); | ||||
| 	background-clip: padding-box; | ||||
| 	} | ||||
| 
 | ||||
| 
 | ||||
| /* popup */ | ||||
| 
 | ||||
| .leaflet-popup { | ||||
| 	position: absolute; | ||||
| 	text-align: center; | ||||
| 	margin-bottom: 20px; | ||||
| 	} | ||||
| .leaflet-popup-content-wrapper { | ||||
| 	padding: 1px; | ||||
| 	text-align: left; | ||||
| 	border-radius: 12px; | ||||
| 	} | ||||
| .leaflet-popup-content { | ||||
| 	margin: 13px 19px; | ||||
| 	line-height: 1.4; | ||||
| 	} | ||||
| .leaflet-popup-content p { | ||||
| 	margin: 18px 0; | ||||
| 	} | ||||
| .leaflet-popup-tip-container { | ||||
| 	width: 40px; | ||||
| 	height: 20px; | ||||
| 	position: absolute; | ||||
| 	left: 50%; | ||||
| 	margin-left: -20px; | ||||
| 	overflow: hidden; | ||||
| 	pointer-events: none; | ||||
| 	} | ||||
| .leaflet-popup-tip { | ||||
| 	width: 17px; | ||||
| 	height: 17px; | ||||
| 	padding: 1px; | ||||
| 
 | ||||
| 	margin: -10px auto 0; | ||||
| 
 | ||||
| 	-webkit-transform: rotate(45deg); | ||||
| 	   -moz-transform: rotate(45deg); | ||||
| 	    -ms-transform: rotate(45deg); | ||||
| 	     -o-transform: rotate(45deg); | ||||
| 	        transform: rotate(45deg); | ||||
| 	} | ||||
| .leaflet-popup-content-wrapper, | ||||
| .leaflet-popup-tip { | ||||
| 	background: white; | ||||
| 	color: #333; | ||||
| 	box-shadow: 0 3px 14px rgba(0,0,0,0.4); | ||||
| 	} | ||||
| .leaflet-container a.leaflet-popup-close-button { | ||||
| 	position: absolute; | ||||
| 	top: 0; | ||||
| 	right: 0; | ||||
| 	padding: 4px 4px 0 0; | ||||
| 	border: none; | ||||
| 	text-align: center; | ||||
| 	width: 18px; | ||||
| 	height: 14px; | ||||
| 	font: 16px/14px Tahoma, Verdana, sans-serif; | ||||
| 	color: #c3c3c3; | ||||
| 	text-decoration: none; | ||||
| 	font-weight: bold; | ||||
| 	background: transparent; | ||||
| 	} | ||||
| .leaflet-container a.leaflet-popup-close-button:hover { | ||||
| 	color: #999; | ||||
| 	} | ||||
| .leaflet-popup-scrolled { | ||||
| 	overflow: auto; | ||||
| 	border-bottom: 1px solid #ddd; | ||||
| 	border-top: 1px solid #ddd; | ||||
| 	} | ||||
| 
 | ||||
| .leaflet-oldie .leaflet-popup-content-wrapper { | ||||
| 	zoom: 1; | ||||
| 	} | ||||
| .leaflet-oldie .leaflet-popup-tip { | ||||
| 	width: 24px; | ||||
| 	margin: 0 auto; | ||||
| 
 | ||||
| 	-ms-filter: "progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678)"; | ||||
| 	filter: progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678); | ||||
| 	} | ||||
| .leaflet-oldie .leaflet-popup-tip-container { | ||||
| 	margin-top: -1px; | ||||
| 	} | ||||
| 
 | ||||
| .leaflet-oldie .leaflet-control-zoom, | ||||
| .leaflet-oldie .leaflet-control-layers, | ||||
| .leaflet-oldie .leaflet-popup-content-wrapper, | ||||
| .leaflet-oldie .leaflet-popup-tip { | ||||
| 	border: 1px solid #999; | ||||
| 	} | ||||
| 
 | ||||
| 
 | ||||
| /* div icon */ | ||||
| 
 | ||||
| .leaflet-div-icon { | ||||
| 	background: #fff; | ||||
| 	border: 1px solid #666; | ||||
| 	} | ||||
| 
 | ||||
| 
 | ||||
| /* Tooltip */ | ||||
| /* Base styles for the element that has a tooltip */ | ||||
| .leaflet-tooltip { | ||||
| 	position: absolute; | ||||
| 	padding: 6px; | ||||
| 	background-color: #fff; | ||||
| 	border: 1px solid #fff; | ||||
| 	border-radius: 3px; | ||||
| 	color: #222; | ||||
| 	white-space: nowrap; | ||||
| 	-webkit-user-select: none; | ||||
| 	-moz-user-select: none; | ||||
| 	-ms-user-select: none; | ||||
| 	user-select: none; | ||||
| 	pointer-events: none; | ||||
| 	box-shadow: 0 1px 3px rgba(0,0,0,0.4); | ||||
| 	} | ||||
| .leaflet-tooltip.leaflet-clickable { | ||||
| 	cursor: pointer; | ||||
| 	pointer-events: auto; | ||||
| 	} | ||||
| .leaflet-tooltip-top:before, | ||||
| .leaflet-tooltip-bottom:before, | ||||
| .leaflet-tooltip-left:before, | ||||
| .leaflet-tooltip-right:before { | ||||
| 	position: absolute; | ||||
| 	pointer-events: none; | ||||
| 	border: 6px solid transparent; | ||||
| 	background: transparent; | ||||
| 	content: ""; | ||||
| 	} | ||||
| 
 | ||||
| /* Directions */ | ||||
| 
 | ||||
| .leaflet-tooltip-bottom { | ||||
| 	margin-top: 6px; | ||||
| } | ||||
| .leaflet-tooltip-top { | ||||
| 	margin-top: -6px; | ||||
| } | ||||
| .leaflet-tooltip-bottom:before, | ||||
| .leaflet-tooltip-top:before { | ||||
| 	left: 50%; | ||||
| 	margin-left: -6px; | ||||
| 	} | ||||
| .leaflet-tooltip-top:before { | ||||
| 	bottom: 0; | ||||
| 	margin-bottom: -12px; | ||||
| 	border-top-color: #fff; | ||||
| 	} | ||||
| .leaflet-tooltip-bottom:before { | ||||
| 	top: 0; | ||||
| 	margin-top: -12px; | ||||
| 	margin-left: -6px; | ||||
| 	border-bottom-color: #fff; | ||||
| 	} | ||||
| .leaflet-tooltip-left { | ||||
| 	margin-left: -6px; | ||||
| } | ||||
| .leaflet-tooltip-right { | ||||
| 	margin-left: 6px; | ||||
| } | ||||
| .leaflet-tooltip-left:before, | ||||
| .leaflet-tooltip-right:before { | ||||
| 	top: 50%; | ||||
| 	margin-top: -6px; | ||||
| 	} | ||||
| .leaflet-tooltip-left:before { | ||||
| 	right: 0; | ||||
| 	margin-right: -12px; | ||||
| 	border-left-color: #fff; | ||||
| 	} | ||||
| .leaflet-tooltip-right:before { | ||||
| 	left: 0; | ||||
| 	margin-left: -12px; | ||||
| 	border-right-color: #fff; | ||||
| 	} | ||||
							
								
								
									
										3
									
								
								resources/public/js/lib/package-lock.json
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								resources/public/js/lib/package-lock.json
									
									
									
										generated
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,3 @@ | |||
| { | ||||
|   "lockfileVersion": 1 | ||||
| } | ||||
							
								
								
									
										3
									
								
								resources/public/js/package-lock.json
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								resources/public/js/package-lock.json
									
									
									
										generated
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,3 @@ | |||
| { | ||||
|   "lockfileVersion": 1 | ||||
| } | ||||
							
								
								
									
										40
									
								
								src/clj/geocsv/handler serves resources.clj
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								src/clj/geocsv/handler serves resources.clj
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,40 @@ | |||
| (ns geocsv.handler | ||||
|   (:require [compojure.core :refer [routes wrap-routes]] | ||||
|             [geocsv.env :refer [defaults]] | ||||
|             [geocsv.middleware :as middleware] | ||||
|             [geocsv.layout :refer [error-page]] | ||||
|             [geocsv.routes.home :refer [home-routes]] | ||||
|             [geocsv.routes.json :refer [json-routes]] | ||||
|             [reitit.ring :as ring] | ||||
|             [ring.middleware.content-type :refer [wrap-content-type]] | ||||
|             [ring.middleware.webjars :refer [wrap-webjars]] | ||||
|             [mount.core :as mount])) | ||||
| 
 | ||||
| (mount/defstate init-app | ||||
|   :start ((or (:init defaults) (fn []))) | ||||
|   :stop  ((or (:stop defaults) (fn [])))) | ||||
| 
 | ||||
| (mount/defstate app-routes | ||||
|   :start | ||||
|   (ring/ring-handler | ||||
|     (ring/router | ||||
|       [(home-routes) | ||||
| ;;        (-> #'json-routes | ||||
| ;;            (wrap-routes middleware/wrap-csrf) | ||||
| ;;            (wrap-routes middleware/wrap-formats)) | ||||
|        ]) | ||||
|     (ring/routes | ||||
|       (ring/create-resource-handler | ||||
|         {:path "/"}) | ||||
|       (wrap-content-type | ||||
|         (wrap-webjars (constantly nil))) | ||||
|       (ring/create-default-handler | ||||
|         {:not-found | ||||
|          (constantly (error-page {:status 404, :title "404 - Page not found"})) | ||||
|          :method-not-allowed | ||||
|          (constantly (error-page {:status 405, :title "405 - Not allowed"})) | ||||
|          :not-acceptable | ||||
|          (constantly (error-page {:status 406, :title "406 - Not acceptable"}))})))) | ||||
| 
 | ||||
| (defn app [] | ||||
|   (middleware/wrap-base #'app-routes)) | ||||
|  | @ -22,10 +22,13 @@ | |||
|   (routes | ||||
|     (-> #'home-routes | ||||
|         (wrap-routes middleware/wrap-csrf) | ||||
|         (wrap-routes middleware/wrap-formats)) | ||||
|         (wrap-routes middleware/wrap-formats) | ||||
|         wrap-webjars) | ||||
|     (-> #'rest-routes | ||||
|         (wrap-routes middleware/wrap-csrf) | ||||
|         (wrap-routes middleware/wrap-formats)) | ||||
| ;;     (ring/create-resource-handler | ||||
| ;;          {:path "/"}) | ||||
|    (route/resources "/") | ||||
|     (route/not-found | ||||
|       (:body | ||||
|  |  | |||
|  | @ -6,6 +6,7 @@ | |||
|             [clojure.java.io :as io] | ||||
|             [clojure.string :as s] | ||||
|             [clojure.tools.logging :as log] | ||||
|             [cpath-clj.core :as cp] | ||||
|             [compojure.core :refer [defroutes GET POST]] | ||||
|             [csv2edn.csv2edn :refer :all] | ||||
|             [noir.response :as nresponse] | ||||
|  | @ -37,19 +38,18 @@ | |||
| ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | ||||
| 
 | ||||
| (defn get-pin-image-names | ||||
|   "Return the category names for which we have pin images; `request` is ignored. | ||||
| 
 | ||||
|   This looks odd - why not file-seq over the directory? - but the answer is we | ||||
|   may be running in a jar file, and if we are that will fail." | ||||
|   [request] | ||||
|   (ar/do-or-server-fail | ||||
|     (map | ||||
|       #(s/replace (.getName %) #"-pin\.png$" "") | ||||
|       (let [grammar-matcher (.getPathMatcher | ||||
|                             (java.nio.file.FileSystems/getDefault) | ||||
|                             "glob:*-pin.png")] | ||||
|       #(s/replace (s/replace (str %) #"-pin\.png$" "") "/" "") | ||||
|       (->> "public/img/map-pins" | ||||
|            io/resource | ||||
|            io/file | ||||
|            file-seq | ||||
|            (filter #(.isFile %)) | ||||
|            (filter #(.matches grammar-matcher (.getFileName (.toPath %))))))) | ||||
|            cp/resources | ||||
|            keys | ||||
|            (filter #(re-find #".*-pin.png" %)))) | ||||
|     200)) | ||||
| 
 | ||||
| (defn get-data-uri | ||||
|  | @ -72,7 +72,7 @@ | |||
|   "Return JSON formatted data taken from the CSV file with the name `filename` | ||||
|   in the directory `resources/public/data`." | ||||
|   [filename] | ||||
|   (-> (str "public/data/" filename) io/resource io/file io/reader csv->json)) | ||||
|   (-> (str "public/data/" filename) io/resource io/reader csv->json)) | ||||
| 
 | ||||
| (defn get-data | ||||
|   "Return JSON formatted data from the source implied by this `request`." | ||||
|  |  | |||
|  | @ -1,4 +1,4 @@ | |||
| (ns geocsv.ajax | ||||
| (ns geocsv.client.ajax | ||||
|   (:require | ||||
|     [ajax.core :as ajax] | ||||
|     [luminus-transit.time :as time] | ||||
|  | @ -6,11 +6,9 @@ | |||
|     [re-frame.core :as rf])) | ||||
| 
 | ||||
| (defn local-uri? [{:keys [uri]}] | ||||
|   (js/console.log (str "local-uri?: received `" (str uri) "` (type " (type uri) ") as uri")) | ||||
|   (not (re-find #"^\w+?://" (str uri)))) | ||||
| 
 | ||||
| (defn default-headers [request] | ||||
|   (js/console.log (str "default-headers: received `" request "` as request")) | ||||
|   (if (local-uri? request) | ||||
|     (-> request | ||||
|         (update :headers #(merge {"x-csrf-token" js/csrfToken} %))) | ||||
|  | @ -1,17 +1,18 @@ | |||
| (ns geocsv.core | ||||
| (ns geocsv.client.core | ||||
|   (:require | ||||
|     [day8.re-frame.http-fx] | ||||
|     [reagent.core :as r] | ||||
|     [re-frame.core :as rf] | ||||
|     [geocsv.views.map :as mv] | ||||
|     [geocsv.client.gis :as gis] | ||||
|     [geocsv.client.views.map :as mv] | ||||
|     [goog.events :as events] | ||||
|     [goog.history.EventType :as HistoryEventType] | ||||
|     [markdown.core :refer [md->html]] | ||||
|     [geocsv.ajax :as ajax] | ||||
|     [geocsv.events] | ||||
|     [geocsv.client.ajax :as ajax] | ||||
|     [geocsv.client.events] | ||||
|     [reitit.core :as reitit] | ||||
|     [reitit.frontend.easy :as rfe] | ||||
|     [clojure.string :as string]) | ||||
|     [clojure.string :as s]) | ||||
|   (:import goog.History)) | ||||
| 
 | ||||
| (defn nav-link [uri title page] | ||||
|  | @ -39,7 +40,31 @@ | |||
| 
 | ||||
| (defn about-page [] | ||||
|   [:section.section>div.container>div.content | ||||
|    [:img {:src "/img/warning_clojure.png"}]]) | ||||
|    [:img {:src "/img/warning_clojure.png"}] | ||||
|    (when-let [images @(rf/subscribe [:available-pin-images])] | ||||
|      [:div | ||||
|       [:h2 "The following pin images are available on this server"] | ||||
|       (apply | ||||
|         vector | ||||
|         (cons | ||||
|           :ol | ||||
|           (map | ||||
|             #(vector | ||||
|                :ol | ||||
|                [:img | ||||
|                 {:src | ||||
|                  (str | ||||
|                    "img/map-pins/" | ||||
|                    (s/capitalize | ||||
|                    (s/replace | ||||
|                      (s/lower-case | ||||
|                        (str %)) | ||||
|                      #"[^a-z0-9]" "-")) | ||||
|                    "-pin.png") | ||||
|                    :alt %}] | ||||
|                " " | ||||
|                %) | ||||
|             (sort images))))])]) | ||||
| 
 | ||||
| (defn home-page [] | ||||
|   [:section.section>div.container>div.content | ||||
|  | @ -90,6 +115,7 @@ | |||
| 
 | ||||
| (defn init! [] | ||||
|   (rf/dispatch-sync [:initialise-db]) | ||||
|   (rf/dispatch [:fetch-pin-image-names]) | ||||
|   (start-router!) | ||||
|   (ajax/load-interceptors!) | ||||
|   (mount-components)) | ||||
|  | @ -1,6 +1,6 @@ | |||
| (ns ^{:doc "geocsv app initial database." | ||||
|       :author "Simon Brooke"} | ||||
|   geocsv.db) | ||||
|   geocsv.client.db) | ||||
| 
 | ||||
| ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | ||||
| ;;;; | ||||
|  | @ -28,6 +28,14 @@ | |||
| 
 | ||||
| (def default-db | ||||
|   {:page :home | ||||
|    :available-pin-images #{"Planning authority" "unknown"}  ;; need to be fetched from server side | ||||
|    :map {:map-centre [56 -4] | ||||
|          :map-zoom  6}}) | ||||
|    :available-pin-images #{"Planning-authority" | ||||
|                            "Landowner" | ||||
|                            "Unknown" | ||||
|                            "Anchor-customer" | ||||
|                            "Investor" | ||||
|                            "Broadband-supplier" | ||||
|                            "Operator" | ||||
|                            "Other-key-customers" | ||||
|                            "Power-supplier"}  ;; need to be fetched from server side | ||||
|    :latitude 56 | ||||
|    :longitude -4}) | ||||
|  | @ -1,9 +1,9 @@ | |||
| (ns geocsv.events | ||||
| (ns geocsv.client.events | ||||
|   (:require [ajax.core :as ajax] | ||||
|             [ajax.json :refer [json-request-format json-response-format]] | ||||
|             [cemerick.url :refer [url url-encode]] | ||||
|             [geocsv.db :refer [default-db]] | ||||
|             [geocsv.gis :refer [refresh-map-pins]] | ||||
|             [geocsv.client.db :refer [default-db]] | ||||
|             [geocsv.client.gis :refer [compute-centre refresh-map-pins]] | ||||
|             [re-frame.core :as rf] | ||||
|             [reitit.frontend.easy :as rfe] | ||||
|             [reitit.frontend.controllers :as rfc])) | ||||
|  | @ -40,7 +40,6 @@ | |||
|     :query nil | ||||
|     :anchor nil)) | ||||
| 
 | ||||
| 
 | ||||
| ;;dispatchers: keep in alphabetical order, please. | ||||
| (rf/reg-event-fx | ||||
|   :bad-data | ||||
|  | @ -73,6 +72,24 @@ | |||
|                        :on-failure      [:bad-data]} | ||||
|           :db  db}))) | ||||
| 
 | ||||
| (rf/reg-event-fx | ||||
|  :fetch-pin-image-names | ||||
|  (fn [{db :db} _] | ||||
|    (let [uri (assoc source-host | ||||
|                   :path "/get-pin-image-names")] | ||||
|          (js/console.log | ||||
|           (str | ||||
|            "Fetching data: " uri)) | ||||
|          ;; we return a map of (side) effects | ||||
|          {:http-xhrio {:method          :get | ||||
|                        :uri             uri | ||||
|                        :format          (json-request-format) | ||||
|                        :response-format (json-response-format {:keywords? true}) | ||||
|                        :on-success      [:process-pin-image-names] | ||||
|                        ;; ignore :on-failure for now | ||||
|                        } | ||||
|           :db  db}))) | ||||
| 
 | ||||
| (rf/reg-event-fx | ||||
|   :fetch-docs | ||||
|   (fn [_ _] | ||||
|  | @ -120,12 +137,26 @@ | |||
|   ;; TODO: why is this an `-fx`? Does it need to be? | ||||
|   (fn | ||||
|     [{db :db} [_ response]] | ||||
|     (let [data (js->clj response)] | ||||
|     (let [db' (assoc db :data (js->clj response))] | ||||
|       (js/console.log (str "processing fetched JSON data")) | ||||
|       {:db (if-let [data (:data db')] | ||||
|              (let [centre (compute-centre data)] | ||||
|                (if | ||||
|                  (:view db') | ||||
|                  (refresh-map-pins (merge db' centre)) | ||||
|                  db) | ||||
|                db))}))) | ||||
| 
 | ||||
| (rf/reg-event-fx | ||||
|   :process-pin-image-names | ||||
|   (fn | ||||
|     [{db :db} [_ response]] | ||||
|     (let [db' (assoc db :available-pin-images (set response))] | ||||
|     (js/console.log (str "processing pin images")) | ||||
|     {:db (if | ||||
|            (:view db) | ||||
|            (refresh-map-pins (assoc db :data data)) | ||||
|            db)}))) | ||||
|            (:view db') | ||||
|            (refresh-map-pins db') | ||||
|            db')}))) | ||||
| 
 | ||||
| (rf/reg-event-db | ||||
|   :set-docs | ||||
|  | @ -167,11 +198,6 @@ | |||
|       (js/console.log (str "Fetching longitude" v)) | ||||
|       v))) | ||||
| 
 | ||||
| (rf/reg-sub | ||||
|   :map | ||||
|   (fn [db _] | ||||
|     (:map db))) | ||||
| 
 | ||||
| (rf/reg-sub | ||||
|   :route | ||||
|   (fn [db _] | ||||
|  | @ -1,6 +1,6 @@ | |||
| (ns ^{:doc "geocsv app map stuff." | ||||
|       :author "Simon Brooke"} | ||||
|   geocsv.gis | ||||
|   geocsv.client.gis | ||||
|   (:require [ajax.core :refer [GET]] | ||||
|             [ajax.json :refer [json-request-format json-response-format]] | ||||
|             [cljs.reader :refer [read-string]] | ||||
|  | @ -71,33 +71,41 @@ | |||
| 
 | ||||
| (defn pin-image | ||||
|   "Return the name of a suitable pin image for this `record`." | ||||
|   [record] | ||||
|   (let [available @(subscribe [:available-pin-images])] | ||||
|     (js/console.log (str "pin-image: available is of type `" (type available) "`; `(fn? available)` returns " (set? available))) | ||||
|   [db record] | ||||
|   (let [available (:available-pin-images db) | ||||
|         category (s/capitalize | ||||
|                    (s/replace | ||||
|                      (s/lower-case | ||||
|                        (str (:category record))) | ||||
|                      #"[^a-z0-9]" "-"))] | ||||
|     (if | ||||
|       (contains? available (:category record)) | ||||
|       (str | ||||
|         (s/capitalize | ||||
|           (s/replace (s/lower-case (str (:category record))) #"[^a-z0-9]" "-")) "-pin") | ||||
|       "unknown-pin"))) | ||||
|       (available category) | ||||
|       (str category "-pin") | ||||
|       "Unknown-pin"))) | ||||
| 
 | ||||
| (defn popup-content | ||||
|   "Appropriate content for the popup of a map pin for this `record`." | ||||
|   [record] | ||||
|   (if | ||||
|     (map? record) ;; which it should be! | ||||
|     (str | ||||
|       "<h5>" | ||||
|       (:name record) | ||||
|       "</h5><dl>" | ||||
|       (apply | ||||
|         str | ||||
|       (map #(str "<dt>" (name %) "</dt><dd>" (record %) "</dd>") (keys record))) | ||||
|     "</dl>")) | ||||
|         (map | ||||
|           #(str "<dt>" (name %) "</dt><dd>" (record %) "</dd>") | ||||
|           (filter #(record %) (keys record)))) | ||||
|       "</dl>"))) | ||||
| 
 | ||||
| (defn popup-table-content | ||||
|   "Appropriate content for the popup of a map pin for this `record`, as a | ||||
|   table. Obviously this is semantically wrong, but for styling reasons it's | ||||
|   worth trying." | ||||
|   [record] | ||||
|   (if | ||||
|     (map? record) ;; which it should be! | ||||
|     (str | ||||
|       "<h5>" | ||||
|       (:name record) | ||||
|  | @ -106,13 +114,13 @@ | |||
|         str | ||||
|         (map | ||||
|           #(str "<tr><th>" (name %) "</th><td>" (record %) "</td></tr>") | ||||
|         (sort (keys record)))) | ||||
|     "</table>")) | ||||
|           (sort (filter #(record %) (keys record))))) | ||||
|       "</table>"))) | ||||
| 
 | ||||
| (defn add-map-pin | ||||
|   "Add an appropriate map-pin for this `record` in this map `view`, if it | ||||
|   has a valid `:latitude` and `:longitude`." | ||||
|   [record index view] | ||||
|   [db record index view] | ||||
|   (let [lat (:latitude record) | ||||
|         lng (:longitude record)] | ||||
|     (if | ||||
|  | @ -125,7 +133,7 @@ | |||
|                        (clj->js | ||||
|                          {:iconAnchor [16 41] | ||||
|                           :iconSize [32 42] | ||||
|                           :iconUrl (str "img/map-pins/" (pin-image record) ".png") | ||||
|                           :iconUrl (str "img/map-pins/" (pin-image db record) ".png") | ||||
|                           :riseOnHover true | ||||
|                           :shadowAnchor [16 23] | ||||
|                           :shadowSize [57 24] | ||||
|  | @ -150,17 +158,52 @@ | |||
|                    (.removeLayer view %))) | ||||
|     view)) | ||||
| 
 | ||||
| (defn compute-zoom | ||||
|   "See [explanation here](https://leafletjs.com/examples/zoom-levels/). Brief | ||||
|   summary: it's hard, but it doesn't need to be precise." | ||||
|   [min-lat max-lat min-lng max-lng] | ||||
|   (let [n (min (/ 360 (- max-lng min-lng)) (/ 180 (- max-lat min-lat)))] | ||||
|     (first | ||||
|       (remove | ||||
|         nil? | ||||
|         (map | ||||
|           #(if (> (reduce * (repeat 2 %)) n) %) | ||||
|           (range)))))) | ||||
| 
 | ||||
| (defn compute-centre | ||||
|   "Compute, and return as a map with keys `:latitude` and `:longitude`, the | ||||
|   centre of the locations of these records as indicated by the values of their | ||||
|   `:latitude` and `:longitude` keys." | ||||
|   [records] | ||||
|   (let [lats (filter number? (map :latitude records)) | ||||
|         min-lat (apply min lats) | ||||
|         max-lat (apply max lats) | ||||
|         lngs (filter number? (map :longitude records)) | ||||
|         min-lng (apply min lngs) | ||||
|         max-lng (apply max lngs)] | ||||
|     (if-not | ||||
|       (or (empty? lats) (empty? lngs)) | ||||
|       {:latitude (+ min-lat (/ (- max-lat min-lat) 2)) | ||||
|        :longitude (+ min-lng (/ (- max-lng min-lng) 2)) | ||||
|        :zoom (compute-zoom min-lat max-lat min-lng max-lng)} | ||||
|       {}))) | ||||
| 
 | ||||
| (defn refresh-map-pins | ||||
|   "Refresh the map pins on the current map. Side-effecty; liable to be | ||||
|     problematic." | ||||
|   [db] | ||||
|   (let [view (map-remove-pins @(subscribe [:view])) | ||||
|         data (:data db)] | ||||
|         data (:data db) | ||||
|         centre (compute-centre data)] | ||||
|     (if | ||||
|       view | ||||
|       (let [added (remove nil? (map #(add-map-pin %1 %2 view) data (range)))] | ||||
|         (js/console.log (str "Adding " (count added) " pins"))) | ||||
|       (js/console.log "View is not yet ready")) | ||||
|       (let [added (remove nil? (map #(add-map-pin db %1 %2 view) data (range)))] | ||||
|         (js/console.log (str "Adding " (count added) " pins")) | ||||
|         (if | ||||
|           (:latitude centre) | ||||
|           (do | ||||
|             (js/console.log (str "computed centre: " centre)) | ||||
|             (.setView view (clj->js [(:latitude centre) (:longitude centre)]) (:zoom centre)) | ||||
|             (merge db centre)) | ||||
|           db)) | ||||
| 
 | ||||
|       (do (js/console.log "View is not yet ready") db)))) | ||||
|  | @ -1,11 +1,11 @@ | |||
| (ns ^{:doc "a map onto which to project CSV data." | ||||
|       :author "Simon Brooke"} | ||||
|   geocsv.views.map | ||||
|   geocsv.client.views.map | ||||
|   (:require [cljsjs.leaflet] | ||||
|             [re-frame.core :refer [reg-sub subscribe dispatch dispatch-sync]] | ||||
|             [reagent.core :as reagent] | ||||
|             [recalcitrant.core :refer [error-boundary]] | ||||
|             [geocsv.gis :refer [refresh-map-pins get-current-location]])) | ||||
|             [geocsv.client.gis :refer [refresh-map-pins get-current-location]])) | ||||
| 
 | ||||
| ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | ||||
| ;;;; | ||||
|  | @ -61,22 +61,14 @@ | |||
|   (let [view (.setView | ||||
|                (.map js/L | ||||
|                      "map" | ||||
|                      ;; (clj->js {:zoomControl false}) | ||||
|                      ) | ||||
|                #js [56 -4] ;;[@(subscribe [:latitude]) @(subscribe [:longitude])] | ||||
|                      (clj->js {:zoomControl false})) | ||||
|                #js [@(subscribe [:latitude]) @(subscribe [:longitude])] | ||||
|                @(subscribe [:zoom]))] | ||||
|     (.addTo (.tileLayer js/L osm-url | ||||
|                         (clj->js {:attribution osm-attrib | ||||
|                                   :maxZoom 18})) | ||||
|             view) | ||||
|     (dispatch-sync [:set-view view]) | ||||
| ;;     (.on view "moveend" | ||||
| ;;          (fn [_] (let [c (.getCenter view)] | ||||
| ;;                    (js/console.log (str "Moving centre to " c)) | ||||
| ;;                    (dispatch-sync [:set-latitude (.-lat c)]) | ||||
| ;;                    (dispatch-sync [:set-longitude (.-lng c)]) | ||||
| ;;                    (dispatch [:fetch-data])))) | ||||
| ;;    (refresh-map-pins) | ||||
|     view)) | ||||
| 
 | ||||
| (defn map-did-mount | ||||
|  | @ -91,7 +83,7 @@ | |||
| (defn map-render | ||||
|   "Render the actual div containing the map." | ||||
|   [] | ||||
|   [:div#map {:style {:height "1000px"}}]) | ||||
|   [:div#map {:style {:height "800px"}}]) | ||||
| 
 | ||||
| (defn panel | ||||
|   "A reagent class for the map object." | ||||
		Loading…
	
		Reference in a new issue