Last active
July 21, 2019 01:03
-
-
Save alex-hhh/cdedfb550bb68411bd2331c8f4d2c421 to your computer and use it in GitHub Desktop.
Timezone Visualisations -- https://alex-hhh.github.io/2019/05/timezone-visualization.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#lang racket | |
(require json) | |
;; Example 0: Load and manipulate the Timezone GeoJSON file | |
;; You will also need to download timezone data from: | |
;; | |
;; https://github.com/evansiroky/timezone-boundary-builder/releases | |
;; Copyright (c) 2019 Alex Harsányi | |
;; Permission is hereby granted, free of charge, to any person obtaining a | |
;; copy of this software and associated documentation files (the "Software"), | |
;; to deal in the Software without restriction, including without limitation | |
;; the rights to use, copy, modify, merge, publish, distribute, sublicense, | |
;; and/or sell copies of the Software, and to permit persons to whom the | |
;; Software is furnished to do so, subject to the following conditions: | |
;; The above copyright notice and this permission notice shall be included in | |
;; all copies or substantial portions of the Software. | |
;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
;; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
;; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
;; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
;; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING | |
;; FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER | |
;; DEALINGS IN THE SOFTWARE. | |
(define timezone-data (time (call-with-input-file "./combined.json" read-json))) | |
;; Extract a random sample from a json OBJECT -- this can be used on the | |
;; timezone-data to obtain a smaller object which can be printed and inspected | |
;; while developing the functions which manipulate the actual timezone data... | |
(define (random-sample object [max-size 5]) | |
(cond | |
((hash? object) | |
;; for hash tables, copy all the keys, but simplify the values | |
(for/hash ([(k v) (in-hash object)]) | |
(values k (random-sample v max-size)))) | |
((list? object) | |
;; Short lists are copied directly, but we only sample MAX-SIZE random | |
;; values from longer lists. In both cases the values themselves are | |
;; further simplified. | |
(let ([len (length object)]) | |
(if (> len max-size) | |
(let ([samples (sort (build-list max-size (lambda (x) (random len))) <)]) | |
(for/list ([(x position) (in-indexed (in-list object))] | |
#:when (member position samples)) | |
(random-sample x max-size))) | |
(for/list ([x (in-list object)]) | |
(random-sample x max-size))))) | |
(#t | |
;; Any other objects are not simplified. | |
object))) | |
;; Every GeoJSON node will have type and properties keys | |
(define (geojson-type node) | |
(hash-ref node 'type #f)) | |
(define (geojson-properties node) | |
(hash-ref node 'properties (lambda () (hash)))) | |
(define (geojson-features node) | |
(let ([type (geojson-type node)]) | |
(if (equal? type "FeatureCollection") | |
(hash-ref node 'features null) | |
(error (format "geojson-features: bad node type: ~a" type))))) | |
(define (geojson-geometry node) | |
(let ([type (geojson-type node)]) | |
(if (equal? type "Feature") | |
(hash-ref node 'geometry (lambda () (hash))) | |
(error (format "geojson-geometry: bad node type: ~a" type))))) | |
(define (geojson-coordinates node) | |
(let ([type (geojson-type node)]) | |
(if (member type '("Point" "LineString" "Polygon" "MultiPoint" "MultiLineString" "MultiPolygon")) | |
(hash-ref node 'coordinates null) | |
(error (format "geojson-coordinates: bad node type: ~a" type))))) | |
;; Count the number of points in a geometry GeoJSON node | |
(define (geojson-geometry-point-count node) | |
(let ([type (geojson-type node)] | |
[coordinates (geojson-coordinates node)]) | |
(cond ((equal? type "Point") | |
;; Coordinates is a single point | |
1) | |
((equal? type "LineString") | |
;; List of points | |
(length coordinates)) | |
((equal? type "Polygon") | |
;; List of lists of points (main and holes. Count them all | |
(for/fold ([result 0]) | |
([line (in-list coordinates)]) | |
(+ result (length line)))) | |
((equal? type "MultiPoint") | |
;; List of points | |
(length coordinates)) | |
((equal? type "MultiLineString") | |
(for/fold ([result 0]) | |
([line (in-list coordinates)]) | |
(+ result (length line)))) | |
((equal? type "MultiPolygon") | |
(for/fold ([result 0]) | |
([polygon (in-list coordinates)]) | |
(+ result | |
(for/fold ([result 0]) | |
([line (in-list polygon)]) | |
(+ result (length line)))))) | |
(#t | |
(error (format "Unknown geometry type: ~a" type)))))) | |
;; Count the number of points in a GeoJSON node and its sub-nodes. | |
(define (geojson-point-count node) | |
(let ((type (geojson-type node))) | |
(cond ((equal? type "FeatureCollection") | |
(for/fold ([result 0]) | |
([feature (in-list (geojson-features node))]) | |
(+ result (geojson-point-count feature)))) | |
((equal? type "Feature") | |
(let ((geometry (geojson-geometry node))) | |
(geojson-geometry-point-count geometry)))))) | |
;; return the timezone name stored in a GeoJSON feature | |
(define (tz-name node) | |
(let ((properties (geojson-properties node))) | |
(hash-ref properties 'tzid #f))) | |
;; Print a summary of the point count in the timezone data JSON | |
(define (analyze-tz-point-count node) | |
(define nzones (length (geojson-features node))) | |
(define total (geojson-point-count node)) | |
(define tz-point-count | |
(for/list ([feature (in-list (geojson-features node))]) | |
(list (tz-name feature) (geojson-point-count feature)))) | |
(set! tz-point-count | |
(sort tz-point-count | |
(lambda (a b) | |
(match-define (list _ p1) a) | |
(match-define (list _ p2) b) | |
(> p1 p2)))) | |
(printf "Total point count: ~a, total zone count ~a~%" total nzones) | |
(for ([tp (in-list tz-point-count)]) | |
(match-define (list name pcount) tp) | |
(printf "~a ~a~%" name pcount))) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#lang racket | |
(require racket/draw) | |
(define dc (new bitmap-dc% [bitmap (make-object bitmap% 250 250)])) | |
(send* dc | |
(set-pen (send the-pen-list find-or-create-pen "black" 5.0 'solid)) | |
(set-brush (send the-brush-list find-or-create-brush "red" 'solid)) | |
(draw-rectangle 10 10 (- 250 20) (- 250 20))) | |
(define bm (send dc get-bitmap)) | |
(send bm save-file "example1.png" 'png) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#lang racket | |
(require racket/draw) | |
(define dc (new bitmap-dc% [bitmap (make-object bitmap% 250 250)])) | |
(send* dc | |
(set-scale 250/2 250/2) | |
(set-origin 250/2 250/2) | |
(set-pen (send the-pen-list find-or-create-pen "black" 0.00001 'solid)) | |
(set-brush (send the-brush-list find-or-create-brush "black" 'transparent)) | |
(draw-rectangle -1 -1 2 2) | |
(set-brush (send the-brush-list find-or-create-brush "red" 'solid)) | |
(draw-line -1 0 1 0) | |
(draw-line 0 -1 0 1)) | |
(define bm (send dc get-bitmap)) | |
(send bm save-file "example2.png" 'png) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#lang racket | |
(require racket/draw) | |
(define path (new dc-path%)) | |
(send path move-to 1 0) | |
(for ([p (in-range 0 (* 2 pi) (/ (* 2 pi) 5))]) | |
(define x (cos p)) | |
(define y (sin p)) | |
(send path line-to x y)) | |
(send path close) | |
(define dc (new bitmap-dc% [bitmap (make-object bitmap% 250 250)])) | |
(send* dc | |
(set-smoothing 'smoothed) | |
(set-scale 250/2 250/2) | |
(set-origin 250/2 250/2) | |
(set-pen (send the-pen-list find-or-create-pen "black" 0.01 'solid)) | |
(set-brush (send the-brush-list find-or-create-brush "red" 'solid)) | |
(draw-path path)) | |
(define bm (send dc get-bitmap)) | |
(send bm save-file "example3.png" 'png) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#lang racket | |
;; Example 4: Render the timezone data using a small color map | |
;; You will also need to download timezone data from: | |
;; | |
;; https://github.com/evansiroky/timezone-boundary-builder/releases | |
;; Copyright (c) 2019 Alex Harsányi | |
;; Permission is hereby granted, free of charge, to any person obtaining a | |
;; copy of this software and associated documentation files (the "Software"), | |
;; to deal in the Software without restriction, including without limitation | |
;; the rights to use, copy, modify, merge, publish, distribute, sublicense, | |
;; and/or sell copies of the Software, and to permit persons to whom the | |
;; Software is furnished to do so, subject to the following conditions: | |
;; The above copyright notice and this permission notice shall be included in | |
;; all copies or substantial portions of the Software. | |
;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
;; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
;; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
;; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
;; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING | |
;; FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER | |
;; DEALINGS IN THE SOFTWARE. | |
(require json racket/draw math/base) | |
(define (lat-lon->map-point coordinates) | |
(match-define (list lon lat _ ...) coordinates) | |
(define-values (x y) (values (degrees->radians lon) (asinh (tan (degrees->radians lat))))) | |
(list (/ (+ 1 (/ x pi)) 2) (/ (- 1 (/ y pi)) 2))) | |
(define (draw-polygon dc polygons) | |
(define path | |
(for/fold ([path #f]) ([polygon (in-list polygons)] #:unless (null? polygon)) | |
(define sub-path (new dc-path%)) | |
(send/apply sub-path move-to (lat-lon->map-point (car polygon))) | |
(for ([point (in-list (cdr polygon))]) | |
(send/apply sub-path line-to (lat-lon->map-point point))) | |
(send sub-path close) | |
(if path (begin (send path append sub-path) path) sub-path))) | |
(and path (send dc draw-path path 0 0))) | |
(define (draw-feature dc feature) | |
(let* ([geometry (hash-ref feature 'geometry (lambda () (hash)))] | |
[data (hash-ref geometry 'coordinates (lambda () null))]) | |
(case (hash-ref geometry 'type #f) | |
(("Polygon") (draw-polygon dc data)) | |
(("MultiPolygon") (for ([polygon (in-list data)]) (draw-polygon dc polygon))) | |
(else (printf "Skipping ~a geometry" (hash-ref geometry 'type #f)))))) | |
(define color-map | |
(list (make-object color% 78 121 165) | |
(make-object color% 241 143 59) | |
(make-object color% 224 88 91) | |
(make-object color% 119 183 178) | |
(make-object color% 90 161 85) | |
(make-object color% 237 201 88) | |
(make-object color% 175 122 160) | |
(make-object color% 254 158 168) | |
(make-object color% 156 117 97) | |
(make-object color% 186 176 172))) | |
(define (make-timezone-bitmap tzdata width height) | |
(define dc (new bitmap-dc% [bitmap (make-object bitmap% width height)])) | |
(send* dc | |
(set-scale width height) | |
(set-smoothing 'smoothed) | |
(set-pen (send the-pen-list find-or-create-pen "black" (* 0.5 (/ 1 width)) 'solid))) | |
;; Iterate over each feature (timezone) and render it | |
(for ([feature (in-list (hash-ref tzdata 'features))] | |
[color (in-cycle (in-list color-map))]) | |
(send dc set-brush (send the-brush-list find-or-create-brush color 'solid)) | |
(draw-feature dc feature)) | |
(send dc get-bitmap)) | |
(define timezone-data (time (call-with-input-file "./combined.json" read-json))) | |
(define bm (make-timezone-bitmap timezone-data 800 700)) | |
(send bm save-file "timezone-map.png" 'png 100 #:unscaled? #t) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#lang racket | |
(require json racket/draw math/base) | |
;; Example 5: Render the timezone data using a a unique color for each color | |
;; map | |
;; You will also need to download timezone data from: | |
;; | |
;; https://github.com/evansiroky/timezone-boundary-builder/releases | |
;; Copyright (c) 2019 Alex Harsányi | |
;; Permission is hereby granted, free of charge, to any person obtaining a | |
;; copy of this software and associated documentation files (the "Software"), | |
;; to deal in the Software without restriction, including without limitation | |
;; the rights to use, copy, modify, merge, publish, distribute, sublicense, | |
;; and/or sell copies of the Software, and to permit persons to whom the | |
;; Software is furnished to do so, subject to the following conditions: | |
;; The above copyright notice and this permission notice shall be included in | |
;; all copies or substantial portions of the Software. | |
;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
;; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
;; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
;; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
;; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING | |
;; FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER | |
;; DEALINGS IN THE SOFTWARE. | |
(define (lat-lon->map-point coordinates) | |
(match-define (list lon lat _ ...) coordinates) | |
(define-values (x y) (values (degrees->radians lon) (asinh (tan (degrees->radians lat))))) | |
(list (/ (+ 1 (/ x pi)) 2) (/ (- 1 (/ y pi)) 2))) | |
(define (draw-polygon dc polygons) | |
(define path | |
(for/fold ([path #f]) ([polygon (in-list polygons)] #:unless (null? polygon)) | |
(define sub-path (new dc-path%)) | |
(send/apply sub-path move-to (lat-lon->map-point (car polygon))) | |
(for ([point (in-list (cdr polygon))]) | |
(send/apply sub-path line-to (lat-lon->map-point point))) | |
(send sub-path close) | |
(if path (begin (send path append sub-path) path) sub-path))) | |
(and path (send dc draw-path path 0 0))) | |
(define (draw-feature dc feature) | |
(let* ([geometry (hash-ref feature 'geometry (lambda () (hash)))] | |
[data (hash-ref geometry 'coordinates (lambda () null))]) | |
(case (hash-ref geometry 'type #f) | |
(("Polygon") (draw-polygon dc data)) | |
(("MultiPolygon") (for ([polygon (in-list data)]) (draw-polygon dc polygon))) | |
(else (printf "Skipping ~a geometry" (hash-ref geometry 'type #f)))))) | |
(define (tz-name node) | |
(let ((properties (hash-ref node 'properties))) | |
(hash-ref properties 'tzid #f))) | |
(define (allocate-color id) | |
(let* ((b (exact-floor (/ id 100))) | |
(g (exact-floor (/ (- id (* b 100)) 10))) | |
(r (exact-floor (- id (* b 100) (* g 10))))) | |
(make-object color% (* r 28) (* g 28) (* b 28)))) | |
(struct tzdata (name id color) #:transparent) | |
(define (allocate-tzdata node) | |
(define next-id 1) ; start at 1, 0 is reserved for "no tz data", the oceans | |
(for/list ([tznode (in-list (hash-ref node 'features))]) | |
(define name (tz-name tznode)) | |
(define id next-id) | |
(set! next-id (add1 next-id)) | |
(define color (allocate-color id)) | |
(tzdata name id color))) | |
(define (time-zone-color tz-data name) | |
(for/first ([t (in-list tz-data)] #:when (equal? (tzdata-name t) name)) | |
(tzdata-color t))) | |
(define color-map | |
(list (make-object color% 78 121 165) | |
(make-object color% 241 143 59) | |
(make-object color% 224 88 91) | |
(make-object color% 119 183 178) | |
(make-object color% 90 161 85) | |
(make-object color% 237 201 88) | |
(make-object color% 175 122 160) | |
(make-object color% 254 158 168) | |
(make-object color% 156 117 97) | |
(make-object color% 186 176 172))) | |
(define (make-timezone-bitmap tzdata width height) | |
(define tz-data (allocate-tzdata timezone-data)) | |
(define dc (new bitmap-dc% [bitmap (make-object bitmap% width height)])) | |
(send* dc | |
(set-scale width height) | |
(set-smoothing 'smoothed) | |
(set-pen (send the-pen-list find-or-create-pen "black" (* 0.5 (/ 1 width)) 'solid))) | |
;; Iterate over each feature (timezone) and render it | |
(for ([feature (in-list (hash-ref tzdata 'features))]) | |
(define color (time-zone-color tz-data (tz-name feature))) | |
(send dc set-brush (send the-brush-list find-or-create-brush color 'solid)) | |
(draw-feature dc feature)) | |
(send dc get-bitmap)) | |
(define timezone-data (call-with-input-file "./combined.json" read-json)) | |
(define bm (make-timezone-bitmap timezone-data 800 500)) | |
(send bm save-file "timezone-map-2.png" 'png 100 #:unscaled? #t) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#lang racket | |
(require json racket/draw math/base) | |
;; Example 6: Render a subset of the map | |
;; You will also need to download timezone data from: | |
;; | |
;; https://github.com/evansiroky/timezone-boundary-builder/releases | |
;; Copyright (c) 2019 Alex Harsányi | |
;; Permission is hereby granted, free of charge, to any person obtaining a | |
;; copy of this software and associated documentation files (the "Software"), | |
;; to deal in the Software without restriction, including without limitation | |
;; the rights to use, copy, modify, merge, publish, distribute, sublicense, | |
;; and/or sell copies of the Software, and to permit persons to whom the | |
;; Software is furnished to do so, subject to the following conditions: | |
;; The above copyright notice and this permission notice shall be included in | |
;; all copies or substantial portions of the Software. | |
;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
;; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
;; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
;; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
;; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING | |
;; FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER | |
;; DEALINGS IN THE SOFTWARE. | |
(define (lat-lon->map-point coordinates) | |
(match-define (list lon lat _ ...) coordinates) | |
(define-values (x y) (values (degrees->radians lon) (asinh (tan (degrees->radians lat))))) | |
(list (/ (+ 1 (/ x pi)) 2) (/ (- 1 (/ y pi)) 2))) | |
(define (draw-polygon dc polygons) | |
(define path | |
(for/fold ([path #f]) ([polygon (in-list polygons)] #:unless (null? polygon)) | |
(define sub-path (new dc-path%)) | |
(send/apply sub-path move-to (lat-lon->map-point (car polygon))) | |
(for ([point (in-list (cdr polygon))]) | |
(send/apply sub-path line-to (lat-lon->map-point point))) | |
(send sub-path close) | |
(if path (begin (send path append sub-path) path) sub-path))) | |
(and path (send dc draw-path path 0 0))) | |
(define (draw-feature dc feature) | |
(let* ([geometry (hash-ref feature 'geometry (lambda () (hash)))] | |
[data (hash-ref geometry 'coordinates (lambda () null))]) | |
(case (hash-ref geometry 'type #f) | |
(("Polygon") (draw-polygon dc data)) | |
(("MultiPolygon") (for ([polygon (in-list data)]) (draw-polygon dc polygon))) | |
(else (printf "Skipping ~a geometry" (hash-ref geometry 'type #f)))))) | |
(define (tz-name node) | |
(let ((properties (hash-ref node 'properties))) | |
(hash-ref properties 'tzid #f))) | |
(define color-map | |
(list (make-object color% 78 121 165) | |
(make-object color% 241 143 59) | |
(make-object color% 224 88 91) | |
(make-object color% 119 183 178) | |
(make-object color% 90 161 85) | |
(make-object color% 237 201 88) | |
(make-object color% 175 122 160) | |
(make-object color% 254 158 168) | |
(make-object color% 156 117 97) | |
(make-object color% 186 176 172))) | |
(define (setup-dc dc min-coord max-coord) | |
(define-values (width height) (send dc get-size)) | |
(match-define (list xmin ymin) (lat-lon->map-point min-coord)) | |
(match-define (list xmax ymax) (lat-lon->map-point max-coord)) | |
(define-values (xscale yscale) (values (/ width (- xmax xmin)) (/ height (- ymax ymin)))) | |
(define-values (xorigin yorigin) (values (- (* xmin xscale)) (- (* ymin yscale)))) | |
(send* dc | |
(set-origin xorigin yorigin) | |
(set-scale xscale yscale) | |
(set-smoothing 'smoothed) | |
(set-pen (send the-pen-list find-or-create-pen "black" (* 0.5 (/ 1 xscale)) 'solid)))) | |
(define (make-timezone-bitmap tzdata min-coord max-coord width height) | |
(define dc (new bitmap-dc% [bitmap (make-object bitmap% width height)])) | |
(setup-dc dc min-coord max-coord) | |
;; Iterate over each feature (timezone) and render it | |
(for ([feature (in-list (hash-ref tzdata 'features))] | |
[color (in-cycle color-map)]) | |
(send dc set-brush (send the-brush-list find-or-create-brush color 'solid)) | |
(draw-feature dc feature)) | |
(send dc get-bitmap)) | |
(define timezone-data (call-with-input-file "./combined.json" read-json)) | |
(define bm (make-timezone-bitmap timezone-data | |
(list -13.008 61.481) | |
(list 33.02 30.894) | |
500 500)) | |
(send bm save-file "timezone-map-3.png" 'png 100 #:unscaled? #t) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment