-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathdb.json
1 lines (1 loc) · 442 KB
/
db.json
1
{"meta":{"version":1,"warehouse":"4.0.0"},"models":{"Asset":[{"_id":"themes/cactus/source/css/style.styl","path":"css/style.styl","modified":0,"renderable":1},{"_id":"themes/cactus/source/css/rtl.styl","path":"css/rtl.styl","modified":0,"renderable":1},{"_id":"themes/cactus/source/js/main.js","path":"js/main.js","modified":0,"renderable":1},{"_id":"themes/cactus/source/js/search.js","path":"js/search.js","modified":0,"renderable":1},{"_id":"themes/cactus/source/images/favicon-192x192.png","path":"images/favicon-192x192.png","modified":0,"renderable":1},{"_id":"themes/cactus/source/images/apple-touch-icon.png","path":"images/apple-touch-icon.png","modified":0,"renderable":1},{"_id":"themes/cactus/source/images/favicon.ico","path":"images/favicon.ico","modified":0,"renderable":1},{"_id":"themes/cactus/source/images/logo.png","path":"images/logo.png","modified":0,"renderable":1},{"_id":"themes/cactus/source/lib/clipboard/clipboard.min.js","path":"lib/clipboard/clipboard.min.js","modified":0,"renderable":1},{"_id":"themes/cactus/source/lib/jquery/jquery.min.js","path":"lib/jquery/jquery.min.js","modified":0,"renderable":1},{"_id":"themes/cactus/source/lib/meslo-LG/MesloLGL-Italic.ttf","path":"lib/meslo-LG/MesloLGL-Italic.ttf","modified":0,"renderable":1},{"_id":"themes/cactus/source/lib/meslo-LG/MesloLGL-BoldItalic.ttf","path":"lib/meslo-LG/MesloLGL-BoldItalic.ttf","modified":0,"renderable":1},{"_id":"themes/cactus/source/lib/meslo-LG/MesloLGL-Bold.ttf","path":"lib/meslo-LG/MesloLGL-Bold.ttf","modified":0,"renderable":1},{"_id":"themes/cactus/source/lib/meslo-LG/MesloLGM-BoldItalic.ttf","path":"lib/meslo-LG/MesloLGM-BoldItalic.ttf","modified":0,"renderable":1},{"_id":"themes/cactus/source/lib/meslo-LG/MesloLGM-Bold.ttf","path":"lib/meslo-LG/MesloLGM-Bold.ttf","modified":0,"renderable":1},{"_id":"themes/cactus/source/lib/meslo-LG/MesloLGL-Regular.ttf","path":"lib/meslo-LG/MesloLGL-Regular.ttf","modified":0,"renderable":1},{"_id":"themes/cactus/source/lib/meslo-LG/MesloLGM-Italic.ttf","path":"lib/meslo-LG/MesloLGM-Italic.ttf","modified":0,"renderable":1},{"_id":"themes/cactus/source/lib/meslo-LG/MesloLGS-BoldItalic.ttf","path":"lib/meslo-LG/MesloLGS-BoldItalic.ttf","modified":0,"renderable":1},{"_id":"themes/cactus/source/lib/meslo-LG/MesloLGM-Regular.ttf","path":"lib/meslo-LG/MesloLGM-Regular.ttf","modified":0,"renderable":1},{"_id":"themes/cactus/source/lib/meslo-LG/MesloLGS-Bold.ttf","path":"lib/meslo-LG/MesloLGS-Bold.ttf","modified":0,"renderable":1},{"_id":"themes/cactus/source/lib/meslo-LG/MesloLGS-Regular.ttf","path":"lib/meslo-LG/MesloLGS-Regular.ttf","modified":0,"renderable":1},{"_id":"themes/cactus/source/lib/meslo-LG/MesloLGS-Italic.ttf","path":"lib/meslo-LG/MesloLGS-Italic.ttf","modified":0,"renderable":1},{"_id":"themes/cactus/source/lib/vazir-font/Vazir-Black.eot","path":"lib/vazir-font/Vazir-Black.eot","modified":0,"renderable":1},{"_id":"themes/cactus/source/lib/vazir-font/Vazir-Black.ttf","path":"lib/vazir-font/Vazir-Black.ttf","modified":0,"renderable":1},{"_id":"themes/cactus/source/lib/vazir-font/Vazir-Black.woff","path":"lib/vazir-font/Vazir-Black.woff","modified":0,"renderable":1},{"_id":"themes/cactus/source/lib/vazir-font/Vazir-Bold.eot","path":"lib/vazir-font/Vazir-Bold.eot","modified":0,"renderable":1},{"_id":"themes/cactus/source/lib/vazir-font/Vazir-Black.woff2","path":"lib/vazir-font/Vazir-Black.woff2","modified":0,"renderable":1},{"_id":"themes/cactus/source/lib/vazir-font/Vazir-Bold.ttf","path":"lib/vazir-font/Vazir-Bold.ttf","modified":0,"renderable":1},{"_id":"themes/cactus/source/lib/vazir-font/Vazir-Bold.woff2","path":"lib/vazir-font/Vazir-Bold.woff2","modified":0,"renderable":1},{"_id":"themes/cactus/source/lib/vazir-font/Vazir-Bold.woff","path":"lib/vazir-font/Vazir-Bold.woff","modified":0,"renderable":1},{"_id":"themes/cactus/source/lib/vazir-font/Vazir-Light.eot","path":"lib/vazir-font/Vazir-Light.eot","modified":0,"renderable":1},{"_id":"themes/cactus/source/lib/vazir-font/Vazir-Light.ttf","path":"lib/vazir-font/Vazir-Light.ttf","modified":0,"renderable":1},{"_id":"themes/cactus/source/lib/vazir-font/Vazir-Light.woff","path":"lib/vazir-font/Vazir-Light.woff","modified":0,"renderable":1},{"_id":"themes/cactus/source/lib/vazir-font/Vazir-Light.woff2","path":"lib/vazir-font/Vazir-Light.woff2","modified":0,"renderable":1},{"_id":"themes/cactus/source/lib/vazir-font/Vazir-Medium.eot","path":"lib/vazir-font/Vazir-Medium.eot","modified":0,"renderable":1},{"_id":"themes/cactus/source/lib/vazir-font/Vazir-Medium.ttf","path":"lib/vazir-font/Vazir-Medium.ttf","modified":0,"renderable":1},{"_id":"themes/cactus/source/lib/vazir-font/Vazir-Medium.woff","path":"lib/vazir-font/Vazir-Medium.woff","modified":0,"renderable":1},{"_id":"themes/cactus/source/lib/vazir-font/Vazir-Medium.woff2","path":"lib/vazir-font/Vazir-Medium.woff2","modified":0,"renderable":1},{"_id":"themes/cactus/source/lib/vazir-font/Vazir-Thin.eot","path":"lib/vazir-font/Vazir-Thin.eot","modified":0,"renderable":1},{"_id":"themes/cactus/source/lib/vazir-font/Vazir-Thin.ttf","path":"lib/vazir-font/Vazir-Thin.ttf","modified":0,"renderable":1},{"_id":"themes/cactus/source/lib/vazir-font/Vazir-Thin.woff","path":"lib/vazir-font/Vazir-Thin.woff","modified":0,"renderable":1},{"_id":"themes/cactus/source/lib/vazir-font/Vazir-Thin.woff2","path":"lib/vazir-font/Vazir-Thin.woff2","modified":0,"renderable":1},{"_id":"themes/cactus/source/lib/vazir-font/Vazir.eot","path":"lib/vazir-font/Vazir.eot","modified":0,"renderable":1},{"_id":"themes/cactus/source/lib/vazir-font/Vazir.ttf","path":"lib/vazir-font/Vazir.ttf","modified":0,"renderable":1},{"_id":"themes/cactus/source/lib/vazir-font/Vazir.woff","path":"lib/vazir-font/Vazir.woff","modified":0,"renderable":1},{"_id":"themes/cactus/source/lib/vazir-font/Vazir.woff2","path":"lib/vazir-font/Vazir.woff2","modified":0,"renderable":1},{"_id":"themes/cactus/source/lib/vazir-font/font-face.css","path":"lib/vazir-font/font-face.css","modified":0,"renderable":1},{"_id":"themes/cactus/source/lib/font-awesome/css/all.min.css","path":"lib/font-awesome/css/all.min.css","modified":0,"renderable":1},{"_id":"themes/cactus/source/lib/font-awesome/webfonts/fa-brands-400.eot","path":"lib/font-awesome/webfonts/fa-brands-400.eot","modified":0,"renderable":1},{"_id":"themes/cactus/source/lib/font-awesome/webfonts/fa-brands-400.svg","path":"lib/font-awesome/webfonts/fa-brands-400.svg","modified":0,"renderable":1},{"_id":"themes/cactus/source/lib/font-awesome/webfonts/fa-brands-400.ttf","path":"lib/font-awesome/webfonts/fa-brands-400.ttf","modified":0,"renderable":1},{"_id":"themes/cactus/source/lib/font-awesome/webfonts/fa-brands-400.woff","path":"lib/font-awesome/webfonts/fa-brands-400.woff","modified":0,"renderable":1},{"_id":"themes/cactus/source/lib/font-awesome/webfonts/fa-brands-400.woff2","path":"lib/font-awesome/webfonts/fa-brands-400.woff2","modified":0,"renderable":1},{"_id":"themes/cactus/source/lib/font-awesome/webfonts/fa-regular-400.eot","path":"lib/font-awesome/webfonts/fa-regular-400.eot","modified":0,"renderable":1},{"_id":"themes/cactus/source/lib/font-awesome/webfonts/fa-regular-400.svg","path":"lib/font-awesome/webfonts/fa-regular-400.svg","modified":0,"renderable":1},{"_id":"themes/cactus/source/lib/font-awesome/webfonts/fa-regular-400.ttf","path":"lib/font-awesome/webfonts/fa-regular-400.ttf","modified":0,"renderable":1},{"_id":"themes/cactus/source/lib/font-awesome/webfonts/fa-regular-400.woff","path":"lib/font-awesome/webfonts/fa-regular-400.woff","modified":0,"renderable":1},{"_id":"themes/cactus/source/lib/font-awesome/webfonts/fa-regular-400.woff2","path":"lib/font-awesome/webfonts/fa-regular-400.woff2","modified":0,"renderable":1},{"_id":"themes/cactus/source/lib/font-awesome/webfonts/fa-solid-900.eot","path":"lib/font-awesome/webfonts/fa-solid-900.eot","modified":0,"renderable":1},{"_id":"themes/cactus/source/lib/font-awesome/webfonts/fa-solid-900.svg","path":"lib/font-awesome/webfonts/fa-solid-900.svg","modified":0,"renderable":1},{"_id":"themes/cactus/source/lib/font-awesome/webfonts/fa-solid-900.ttf","path":"lib/font-awesome/webfonts/fa-solid-900.ttf","modified":0,"renderable":1},{"_id":"themes/cactus/source/lib/font-awesome/webfonts/fa-solid-900.woff","path":"lib/font-awesome/webfonts/fa-solid-900.woff","modified":0,"renderable":1},{"_id":"themes/cactus/source/lib/font-awesome/webfonts/fa-solid-900.woff2","path":"lib/font-awesome/webfonts/fa-solid-900.woff2","modified":0,"renderable":1},{"_id":"themes/cactus/source/lib/justified-gallery/css/justifiedGallery.min.css","path":"lib/justified-gallery/css/justifiedGallery.min.css","modified":0,"renderable":1},{"_id":"themes/cactus/source/lib/justified-gallery/js/jquery.justifiedGallery.min.js","path":"lib/justified-gallery/js/jquery.justifiedGallery.min.js","modified":0,"renderable":1},{"_id":"source/gallery/data/seattle-neighborhoods.geojson","path":"gallery/data/seattle-neighborhoods.geojson","modified":0,"renderable":0},{"_id":"source/gallery/data/counties-wa.geojson","path":"gallery/data/counties-wa.geojson","modified":0,"renderable":0},{"_id":"source/gallery/data/counties-wa-point.geojson","path":"gallery/data/counties-wa-point.geojson","modified":0,"renderable":0},{"_id":"source/gallery/data/states.geojson","path":"gallery/data/states.geojson","modified":0,"renderable":0}],"Cache":[{"_id":"source/gallery/fl-straits.html","hash":"066542ba93b6c24850b830193dcda9307f484e4d","modified":1643251051653},{"_id":"source/_data/projects.json","hash":"41560ee54ed5654d144241816768f409af3a5b2b","modified":1643251051589},{"_id":"source/gallery/covid-states.html","hash":"1a2248353d59a639b1c599212bb0249bc267be66","modified":1643251051596},{"_id":"source/gallery/covid-wa-county.html","hash":"678481e8f48b7b85862fdd4de082fd264163ea56","modified":1643251051596},{"_id":"source/gallery/seattle-neighborhoods.html","hash":"3f33d7cf22101bf95189254b0469c999cd7260a9","modified":1643251051653},{"_id":"source/uses/index.md","hash":"a8b86050cae86e37e5f99b65889c3ddc1f7344ce","modified":1643251051654},{"_id":"source/gallery/index.md","hash":"b9f3c4a3a21a9fa5b85e37985a737cbf7d375c8b","modified":1643251051653},{"_id":"source/_posts/jq-geojson.md","hash":"37d248681fd965992ae803fb364fccb185cb42d4","modified":1643251051589},{"_id":"source/_posts/koop-vector-tile.md","hash":"70dbf6f7cbc34f550d2d988c3904f47428ed0ac8","modified":1643251051589},{"_id":"source/_posts/mappreview.md","hash":"3e8b83274f1009d1863c70dc965e981ac67d2e34","modified":1643251051593},{"_id":"source/_posts/kotlin-cli.md","hash":"d5247b6e7a99eb75544caa9bdcaaef78628625ba","modified":1643251051593},{"_id":"source/_posts/spatial-references.md","hash":"9a5e0e5857d8f5662222e9aab3bad6824b134bc8","modified":1643251051596},{"_id":"source/_posts/kotlin-mulitplatform.md","hash":"e840e82ebd57d9ad53acd21f96b70344581ef031","modified":1643251051593},{"_id":"source/_posts/sqldelight.md","hash":"40ccd26a822df0c9fe719463d9cef18d920c5ad9","modified":1643251051596},{"_id":"source/gallery/data/counties-wa-point.geojson","hash":"7b3e17138a630de995b87044149852ca39ec4c3e","modified":1643503031598},{"_id":"source/gallery/style/map.css","hash":"b4ba1c9cd3ae4971fe789adeb4c45751220db063","modified":1643251051654},{"_id":"source/_posts/pgkoop.md","hash":"d4388b5930afdea59cec71fc695fe540b9087ed5","modified":1643251051595},{"_id":"source/_posts/kotlin-cli/app-structure.png","hash":"b09168e1cf41d798eceefc2db349c7602a17dd08","modified":1643251051593},{"_id":"source/gallery/src/covid.js","hash":"743b6f98d4af2c0fb68672943b64b6cddef92e1b","modified":1643251051653},{"_id":"source/gallery/src/fl-straits.js","hash":"5c438500bd121eba8e40cb0103a616fd4c87d5da","modified":1643251051653},{"_id":"source/gallery/src/covid-wa-county.js","hash":"1c054a2baab3d8812fe99428e680be68ab929a3c","modified":1643251051653},{"_id":"source/gallery/src/seattle-json.js","hash":"bd59c15b08a2285a28c632263142f74472e37abc","modified":1643251051653},{"_id":"source/gallery/src/modernizr-custom.js","hash":"55cb4332f0a55c5496b96c6c4e773c00592bbe45","modified":1643251051653},{"_id":"source/gallery/data/counties-wa.geojson","hash":"e8fafe8f48727fb93c31e3fddb90e53299fc43b5","modified":1643503031977},{"_id":"source/_posts/pgkoop/agol.png","hash":"246ba3ca68d5c664a0839a31fac7579c72b12198","modified":1643251051595},{"_id":"source/_posts/pgkoop/qgis.png","hash":"4247c5fe3fc30d93f8492a87bb8f6c93489d86f4","modified":1643251051596},{"_id":"source/_posts/pgkoop/leaflet.png","hash":"2120bffbeb70e404622b6c7d9d4b54573f854743","modified":1643251051595},{"_id":"themes/cactus/.gitignore","hash":"c5345a2c5fa6c136dbe2020a405e541b4755a259","modified":1643251051654},{"_id":"themes/cactus/.jshintrc","hash":"2548bd6ce44422edc7e6f9f68061ab47f26c4f57","modified":1643251051654},{"_id":"themes/cactus/README.md","hash":"859d11941c46be4f7b4a6d62297ccbeb4f732bc9","modified":1643251051654},{"_id":"themes/cactus/LICENSE","hash":"346ece39a983b0e7858c11f785cd846cef9eb875","modified":1643251051654},{"_id":"themes/cactus/.stylintrc","hash":"eb5f48e83657928cb0cbee031373b2cd36ca0083","modified":1643251051654},{"_id":"themes/cactus/gulpfile.js","hash":"0e55606323a45873506c08be6528478c08373e1e","modified":1643251051654},{"_id":"themes/cactus/package.json","hash":"a6060fadd36114d8cb74e7ff4c7d073901b5edcd","modified":1643251051656},{"_id":"themes/cactus/languages/ca.yml","hash":"b79dd2c21dc6697c635e92db1f661a4b8d5d2305","modified":1643251051654},{"_id":"themes/cactus/languages/default.yml","hash":"703548ad90034d4e5207a27eb50f726dc27e4c0c","modified":1643251051654},{"_id":"themes/cactus/_config.yml","hash":"349955b197584d3c714680f0424b9ad09e20df6d","modified":1643251051654},{"_id":"themes/cactus/languages/en.yml","hash":"703548ad90034d4e5207a27eb50f726dc27e4c0c","modified":1643251051654},{"_id":"themes/cactus/languages/fa.yml","hash":"63f32e50953af1c4bd0308a4fca5862b5287c2cb","modified":1643251051654},{"_id":"themes/cactus/languages/it.yml","hash":"62800bcae1f2d2454f87f4bcf4d7593848424f61","modified":1643251051655},{"_id":"themes/cactus/languages/kr.yml","hash":"651fb83991c91b13b53ed55740e5402cf0f1c5e8","modified":1643251051655},{"_id":"themes/cactus/languages/nl.yml","hash":"ac0573352ad2c737a7686bcca498b985e7bd6447","modified":1643251051655},{"_id":"themes/cactus/languages/es.yml","hash":"2b1fc8b0d636123e9ee39017fa20053bd1913a5a","modified":1643251051654},{"_id":"themes/cactus/languages/fr.yml","hash":"4fea266d3c522903f3eee4fffee6e66c44775005","modified":1643251051654},{"_id":"themes/cactus/languages/pt-br.yml","hash":"4859aba788a050c2d5d0b997693b0c8c24b349f7","modified":1643251051655},{"_id":"themes/cactus/languages/pl.yml","hash":"8a2d6dc874d86c38d42c2c861c39590647b5d536","modified":1643251051655},{"_id":"themes/cactus/languages/ru.yml","hash":"81b57fcd1977ef534f4bf303dbc1b4710cc7f057","modified":1643251051655},{"_id":"themes/cactus/languages/vi.yml","hash":"f84893c3ec3e45875c90069e14b17ed3016ed973","modified":1643251051655},{"_id":"themes/cactus/languages/zh-CN.yml","hash":"8f81faaad9a0615b09dbc23868484a55ec958f6f","modified":1643251051655},{"_id":"themes/cactus/languages/zh-TW.yml","hash":"2f4e050c9b35a67f4a7278cec3a949533c2ac16a","modified":1643251051655},{"_id":"themes/cactus/languages/tr.yml","hash":"2702914007e6bade9d6861078c0e179ac05bf48c","modified":1643251051655},{"_id":"themes/cactus/layout/archive.ejs","hash":"53de8817e37be01b3ba8fa5ca31b9cafa2f3c011","modified":1643251051656},{"_id":"themes/cactus/layout/index.ejs","hash":"606d4081d16e45813da46f2c1f278364413ed2b8","modified":1643251051656},{"_id":"themes/cactus/scripts/page_title.js","hash":"fa662dbdb82779af1b95e35ed7ccdf4866a53dee","modified":1643251051657},{"_id":"themes/cactus/scripts/meta.js","hash":"fa6055a39851c9953d033e70c1614547b94dce60","modified":1643251051656},{"_id":"themes/cactus/scripts/thumbnail.js","hash":"df8829fd8c3119650037eba5ec11bdce06acff9d","modified":1643251051657},{"_id":"themes/cactus/layout/layout.ejs","hash":"8504004f2ed78914f806c6699d9bd722318cbe56","modified":1643251051656},{"_id":"themes/cactus/layout/post.ejs","hash":"a7d164ce888a60cd3eddd9c04bc6762428fa66bb","modified":1643251051656},{"_id":"themes/cactus/layout/page.ejs","hash":"c5465d5315a7544aa466b01fd8cfb62917a8bb1d","modified":1643251051656},{"_id":"themes/cactus/scripts/merge-configs.js","hash":"2048c3415d96b17b9d84aa44bc0c25f1210525f8","modified":1643251051656},{"_id":"themes/cactus/layout/_partial/comments.ejs","hash":"4cf8d0059e5f8bc8ae1dd1a426293583fd398052","modified":1643251051655},{"_id":"themes/cactus/layout/_partial/footer.ejs","hash":"c3a80e347cb11022baf5e65fb4d0209b8d205693","modified":1643251051655},{"_id":"themes/cactus/layout/_partial/header.ejs","hash":"6b534801486f6baa989bd351915a9156b838b777","modified":1643251051655},{"_id":"themes/cactus/layout/_partial/head.ejs","hash":"d23f053edf01caddf1e453d0b3a51bc9a8c2fcf8","modified":1643251051655},{"_id":"themes/cactus/layout/_partial/pagination.ejs","hash":"23bf862b3b8a3cd831850504d9b5a24d21b005e7","modified":1643251051655},{"_id":"themes/cactus/layout/_partial/search.ejs","hash":"8b4bf9cf5db0ce762a31fc3baae0f2fc004bece4","modified":1643251051656},{"_id":"themes/cactus/layout/_partial/styles.ejs","hash":"be1b54388eb02176dd4722285dda19e3dce2e62e","modified":1643251051656},{"_id":"themes/cactus/layout/_partial/scripts.ejs","hash":"137d3039f61cae8ae9219eb6771dd367304f9ec3","modified":1643251051656},{"_id":"themes/cactus/source/css/_fonts.styl","hash":"354809b5a64e8a47a66c66fd1a28ac597c1460a6","modified":1643251051657},{"_id":"themes/cactus/source/css/_extend.styl","hash":"b6a4e5905a7515dda66919167531a5ab2b3d1fe2","modified":1643251051657},{"_id":"themes/cactus/source/css/_mixins.styl","hash":"1a9e309523df9685e8d088dcff0a809c58e2c392","modified":1643251051661},{"_id":"themes/cactus/source/css/_util.styl","hash":"2bfeb2e2605dd5235693b00c71a212646d2e0410","modified":1643251051662},{"_id":"themes/cactus/source/css/style.styl","hash":"18b22cfdc7457d81db7694aef5850cc36ff87a77","modified":1643251051663},{"_id":"themes/cactus/source/css/rtl.styl","hash":"ff8700e1626feeb53d905a2df2777bda7d1eca50","modified":1643251051662},{"_id":"themes/cactus/source/images/favicon-192x192.png","hash":"96e6fcbbb13a5914a6131391e210eb7dfd13d692","modified":1643251051663},{"_id":"themes/cactus/source/images/apple-touch-icon.png","hash":"57e2def34682655f41a0be2d083f16765ba7858b","modified":1643251051663},{"_id":"themes/cactus/source/images/favicon.ico","hash":"189f9842bcb79a6f8f9e8445bc8bbd773443826b","modified":1643251051663},{"_id":"themes/cactus/source/js/main.js","hash":"584c5a69ac81a483a1c4377a2e2cf326c2795e7b","modified":1643251051664},{"_id":"themes/cactus/source/js/search.js","hash":"a74d0c601f820160825a2e4ad13618074d714933","modified":1643251051664},{"_id":"themes/cactus/layout/_partial/post/actions_desktop.ejs","hash":"38aadd1ed890303dde582b722486138afee09b0a","modified":1643251051655},{"_id":"themes/cactus/layout/_partial/post/actions_mobile.ejs","hash":"79b234ff3c264e66b2e71c819228e62bf92b48e4","modified":1643251051655},{"_id":"themes/cactus/layout/_partial/post/category.ejs","hash":"b5bfa049f17868fb09d9d2a7e1d5279fa0381d37","modified":1643251051656},{"_id":"themes/cactus/layout/_partial/post/date.ejs","hash":"6f2d1aa9562df343b797d25705f1945323c465fb","modified":1643251051656},{"_id":"themes/cactus/layout/_partial/post/share.ejs","hash":"1a294382bd14d979525b8ed934d807bc7d083e4d","modified":1643251051656},{"_id":"themes/cactus/layout/_partial/post/gallery.ejs","hash":"9aecd8908e8a684f33dc20c02497c0f1774137c7","modified":1643251051656},{"_id":"themes/cactus/layout/_partial/post/title.ejs","hash":"a060f1c6e3718494a6b1d0e1981ea0bf4e549828","modified":1643251051656},{"_id":"themes/cactus/layout/_partial/post/tag.ejs","hash":"e08fae30da060f49c087f6c121868b08eb55c795","modified":1643251051656},{"_id":"themes/cactus/source/css/_colors/classic.styl","hash":"0f0ec41a4165814ce69688425d5ac4d701b7cc70","modified":1643251051657},{"_id":"themes/cactus/source/css/_colors/dark.styl","hash":"1f7fb50cc5225ef225f4dcc377c7e54721d76c69","modified":1643251051657},{"_id":"themes/cactus/source/css/_colors/light.styl","hash":"d09f781cb02394850737b3a9efc6693307d5bf09","modified":1643251051657},{"_id":"themes/cactus/source/css/_colors/white.styl","hash":"2b25ad24573bded8b42f9d80112eab9fadbed1a5","modified":1643251051657},{"_id":"themes/cactus/source/css/_partial/article.styl","hash":"c6a3c395ceb4aacba8c995bcde7b58a7ca501919","modified":1643251051662},{"_id":"themes/cactus/source/css/_partial/comments.styl","hash":"1e90f1fb9d4c155df518cacb5a537e9de9c042c1","modified":1643251051662},{"_id":"themes/cactus/source/css/_partial/categories.styl","hash":"a43f00e61b3507f130b8a3f8108a4eeca147c2a0","modified":1643251051662},{"_id":"themes/cactus/source/css/_partial/archive.styl","hash":"ef8fc52337c4c7b010cad7c427cb78009b30f9d8","modified":1643251051661},{"_id":"themes/cactus/source/css/_partial/header.styl","hash":"519af79eb34ee922b48e6c19aa8f4856e3f76486","modified":1643251051662},{"_id":"themes/cactus/source/css/_partial/index.styl","hash":"59c99f4ea3a73bf47ce030df166c5e33d5de31fb","modified":1643251051662},{"_id":"themes/cactus/source/css/_partial/footer.styl","hash":"14dda7f155bb21e6cd33ca3d8daa5b489b4707b3","modified":1643251051662},{"_id":"themes/cactus/source/css/_partial/tags.styl","hash":"d571d5c7c960300d29c5f0ec3fe1140322ecd6b3","modified":1643251051662},{"_id":"themes/cactus/source/css/_partial/pagination.styl","hash":"950bf517bbe7adb9a9aa4eb5ddec74ffc7598787","modified":1643251051662},{"_id":"themes/cactus/source/css/_partial/search.styl","hash":"159be002780c62a77f46947cf854a7342fba24f4","modified":1643251051662},{"_id":"themes/cactus/source/css/_partial/tooltip.styl","hash":"2daff581ec3efaec840cbfdee512195919c32629","modified":1643251051662},{"_id":"themes/cactus/source/css/_highlight/androidstudio.styl","hash":"2af0861725f97f0ee2ded67c3d2d4548c62b2d16","modified":1643251051657},{"_id":"themes/cactus/source/css/_highlight/agate.styl","hash":"53027913ed8d4f75ac3e49e76aad824f0df62da3","modified":1643251051657},{"_id":"themes/cactus/source/css/_highlight/arduino-light.styl","hash":"15e8572585cd708221c513dea4bdd89d8fe56c10","modified":1643251051657},{"_id":"themes/cactus/source/css/_highlight/ascetic.styl","hash":"32cff3bef6fac3760fe78f203096477052a90552","modified":1643251051657},{"_id":"themes/cactus/source/css/_highlight/atelier-cave-dark.styl","hash":"ce63dd8548688d88254405eedfa75b1d7c82449e","modified":1643251051657},{"_id":"themes/cactus/source/css/_highlight/arta.styl","hash":"b3e81e3e694ceb8deed178adb8b91013c5120e30","modified":1643251051657},{"_id":"themes/cactus/source/css/_highlight/atelier-cave-light.styl","hash":"a5be0744a7ecf4a08f600ade4cfd555afc67bc15","modified":1643251051657},{"_id":"themes/cactus/source/css/_highlight/atelier-dune-dark.styl","hash":"c196ff0ee064af0e507823694ae39020addfc280","modified":1643251051657},{"_id":"themes/cactus/source/css/_highlight/atelier-dune-light.styl","hash":"931435fbc6f974e8ce9e32722680035d248a9dc1","modified":1643251051657},{"_id":"themes/cactus/source/css/_highlight/atelier-estuary-dark.styl","hash":"0bb16a4eff93688f40787abc2f9e56e7d5cc93e7","modified":1643251051658},{"_id":"themes/cactus/source/css/_highlight/atelier-estuary-light.styl","hash":"344276ca9b27e51d4c907f76afe5d13cf8e60bdf","modified":1643251051658},{"_id":"themes/cactus/source/css/_highlight/atelier-forest-dark.styl","hash":"effbc5d75fa87203c847039869c22031b40d5b7d","modified":1643251051658},{"_id":"themes/cactus/source/css/_highlight/atelier-forest-light.styl","hash":"95228d9f2102fad425536aac44b80b2cba1f5950","modified":1643251051658},{"_id":"themes/cactus/source/css/_highlight/atelier-heath-dark.styl","hash":"9a2e9a1d0a01bbdf158560c3ed1c134e098b2c68","modified":1643251051658},{"_id":"themes/cactus/source/css/_highlight/atelier-heath-light.styl","hash":"8c8c2e445abef85273be966d59770e9ced6aac21","modified":1643251051658},{"_id":"themes/cactus/source/css/_highlight/atelier-lakeside-dark.styl","hash":"10ee3882fca7b97a37bd309d2d35fce9868647bb","modified":1643251051658},{"_id":"themes/cactus/source/css/_highlight/atelier-plateau-light.styl","hash":"d1a05fdd1ededc9063d181ab25bad55a164aeb4a","modified":1643251051658},{"_id":"themes/cactus/source/css/_variables.styl","hash":"02079fb71b7d1c01d15fa512a1948ad4cbb416b5","modified":1643251051662},{"_id":"themes/cactus/source/css/_highlight/atelier-lakeside-light.styl","hash":"2c54cb9bdb259ae3b5b29f63ac2469ed34b08578","modified":1643251051658},{"_id":"themes/cactus/source/css/_highlight/atelier-savanna-dark.styl","hash":"e32c1c70def8060fce5e790979a126da650ac642","modified":1643251051658},{"_id":"themes/cactus/source/css/_highlight/atelier-plateau-dark.styl","hash":"84c80e6f67f62fce958d25817c277d2360272617","modified":1643251051658},{"_id":"themes/cactus/source/css/_highlight/atelier-seaside-dark.styl","hash":"2edf385215bbe1985b1a10106525d362667d28c2","modified":1643251051658},{"_id":"themes/cactus/source/css/_highlight/atelier-seaside-light.styl","hash":"0597342da6e2d0c5bdcc7d42dabb07322b1a4177","modified":1643251051658},{"_id":"themes/cactus/source/css/_highlight/atelier-sulphurpool-dark.styl","hash":"538a14321193cd8abf2ddc484306631e54149ffb","modified":1643251051658},{"_id":"themes/cactus/source/css/_highlight/atelier-sulphurpool-light.styl","hash":"efa52713efc468abeeb2b9299704371583b857de","modified":1643251051658},{"_id":"themes/cactus/source/css/_highlight/brown-paper.styl","hash":"c2326ba20a5020a66ca7895258d18833327d4334","modified":1643251051658},{"_id":"themes/cactus/source/css/_highlight/brown-papersq.png","hash":"3a1332ede3a75a3d24f60b6ed69035b72da5e182","modified":1643251051658},{"_id":"themes/cactus/source/css/_highlight/color-brewer.styl","hash":"2a439d6214430e2f45dd4939b4dfe1fe1a20aa0f","modified":1643251051659},{"_id":"themes/cactus/source/css/_highlight/darkula.styl","hash":"9717efa9194837ba3fb4d762997d33075dcf8bfa","modified":1643251051659},{"_id":"themes/cactus/source/css/_highlight/codepen-embed.styl","hash":"8b7b34484f76a6c2c3b1a9e49abb9b382f439ae8","modified":1643251051659},{"_id":"themes/cactus/source/css/_highlight/atelier-savanna-light.styl","hash":"f8244c93711c7cb59dd79d2df966806b30d171ea","modified":1643251051658},{"_id":"themes/cactus/source/css/_highlight/docco.styl","hash":"b1c176378bb275f2e8caa759f36294e42d614bf1","modified":1643251051659},{"_id":"themes/cactus/source/css/_highlight/dark.styl","hash":"f5e6e75958de59e87fc6be3a1668e870e20bc836","modified":1643251051659},{"_id":"themes/cactus/source/css/_highlight/far.styl","hash":"aaac3028f5e33123cd123a583cddc9290c45ec8e","modified":1643251051659},{"_id":"themes/cactus/source/css/_highlight/foundation.styl","hash":"bf8ddc94b4ad995b8b8805b5a4cf95004553fdac","modified":1643251051659},{"_id":"themes/cactus/source/css/_highlight/github-gist.styl","hash":"48211a03d33e7f7ada0b261162bea06676155a71","modified":1643251051659},{"_id":"themes/cactus/source/css/_highlight/googlecode.styl","hash":"bda816beee7b439814b514e6869dc678822be1bc","modified":1643251051659},{"_id":"themes/cactus/source/css/_highlight/grayscale.styl","hash":"bf37d8b8d1e602126c51526f0cc28807440228ed","modified":1643251051659},{"_id":"themes/cactus/source/css/_highlight/github.styl","hash":"3336aeba324c6d34a6fd41fef9b47bc598f7064c","modified":1643251051659},{"_id":"themes/cactus/source/css/_highlight/gruvbox-dark.styl","hash":"76b744c14fd5600bea64731c05df97c2df75523f","modified":1643251051659},{"_id":"themes/cactus/source/css/_highlight/hopscotch.styl","hash":"1378a6bc67a32c0cbff72ab771268b53f9aa586d","modified":1643251051659},{"_id":"themes/cactus/source/css/_highlight/highlightjs.styl","hash":"0e198b7a59191c7a39b641a4ddd22c948edb9358","modified":1643251051659},{"_id":"themes/cactus/source/css/_highlight/hybrid.styl","hash":"b8eb5c69d12f2ee5ebc50265ae271699d7f1a8d3","modified":1643251051659},{"_id":"themes/cactus/source/css/_highlight/ir-black.styl","hash":"53e5d74326a4527b92272bbd6946d4fec92720e8","modified":1643251051659},{"_id":"themes/cactus/source/css/_highlight/idea.styl","hash":"a02967cb51c16a34e0ee895d33ded2b823d35b21","modified":1643251051659},{"_id":"themes/cactus/source/css/_highlight/kimbie.dark.styl","hash":"45dbb168f22d739d0109745d2decd66b5f94e786","modified":1643251051660},{"_id":"themes/cactus/source/css/_highlight/kimbie.light.styl","hash":"61f8baed25be05288c8604d5070afbcd9f183f49","modified":1643251051660},{"_id":"themes/cactus/source/css/_highlight/index.styl","hash":"002d5596f6379cc87dbd43d9145bc764aa666be1","modified":1643251051659},{"_id":"themes/cactus/source/css/_highlight/kimbie.styl","hash":"51b889ca7c6fe178cfbbe28d875a6ea427184441","modified":1643251051660},{"_id":"themes/cactus/source/css/_highlight/magula.styl","hash":"16d323f989b1420a0f72ef989242ece9bf17a456","modified":1643251051660},{"_id":"themes/cactus/source/css/_highlight/mono-blue.styl","hash":"4c89a6ae29de67c0700585af82a60607e85df928","modified":1643251051660},{"_id":"themes/cactus/source/css/_highlight/monokai-sublime.styl","hash":"c385b11345894be7e6ce3c5f08663e199933b8e4","modified":1643251051660},{"_id":"themes/cactus/source/css/_highlight/monokai.styl","hash":"f87be027848ea6bee623a08ad1e17b2f5b7937ee","modified":1643251051660},{"_id":"themes/cactus/source/css/_highlight/obsidian.styl","hash":"199e28326be8590883f0813ebbd54fcfaa4750fd","modified":1643251051660},{"_id":"themes/cactus/source/css/_highlight/paraiso-dark.styl","hash":"f1537bd868579fa018ecdbfd2eb922dcf3ba2cac","modified":1643251051660},{"_id":"themes/cactus/source/css/_highlight/railscasts.styl","hash":"b6674db9210e0c4444e4835fff2d1361f3ebd64c","modified":1643251051660},{"_id":"themes/cactus/source/css/_highlight/paraiso.styl","hash":"75f181eece6b71d033ea0c8d6cf00ae7efb9e29b","modified":1643251051660},{"_id":"themes/cactus/source/css/_highlight/paraiso-light.styl","hash":"d224d1df0eb3395d9eea1344cee945c228af2911","modified":1643251051660},{"_id":"themes/cactus/source/css/_highlight/pojoaque.styl","hash":"4e7b6b046b8575ac749f6aec4e953a62ada27a36","modified":1643251051660},{"_id":"themes/cactus/source/css/_highlight/pojoaque.jpg","hash":"c5fe6533b88b21f8d90d3d03954c6b29baa67791","modified":1643251051660},{"_id":"themes/cactus/source/css/_highlight/school-book.styl","hash":"d43560fe519a931ce6da7d57416d7aa148441b83","modified":1643251051660},{"_id":"themes/cactus/source/css/_highlight/rainbow.styl","hash":"c0cf97aae3e10fdcd10414547a711c9effbc39b8","modified":1643251051660},{"_id":"themes/cactus/source/css/_highlight/school-book.png","hash":"711ec983c874e093bb89eb77afcbdf6741fa61ee","modified":1643251051660},{"_id":"themes/cactus/source/css/_highlight/sunburst.styl","hash":"af3eec0fd56151e55bbd49c31b151f36717611d8","modified":1643251051661},{"_id":"themes/cactus/source/css/_highlight/tomorrow-night-blue.styl","hash":"f24c17d0ab815dcfaab3438cb9fe2ab4839f5e0d","modified":1643251051661},{"_id":"themes/cactus/source/css/_highlight/solarized-light.styl","hash":"aa0dd3fd25c464183b59c5575c9bee8756b397f2","modified":1643251051660},{"_id":"themes/cactus/source/css/_highlight/tomorrow-night-eighties.styl","hash":"28d751075ebabf7d0327a36f725076fe82fdf626","modified":1643251051661},{"_id":"themes/cactus/source/css/_highlight/tomorrow-night-bright.styl","hash":"7674fecb6d27350727dc0d2dc93bc018382ebbd0","modified":1643251051661},{"_id":"themes/cactus/source/css/_highlight/tomorrow.styl","hash":"15779cf6846725c7c35fc56cac39047d7e0aec1c","modified":1643251051661},{"_id":"themes/cactus/source/css/_highlight/tomorrow-night.styl","hash":"16ba09b2db501e4e3e2e7d62595d9bf935bf27c4","modified":1643251051661},{"_id":"themes/cactus/source/css/_highlight/solarized-dark.styl","hash":"90c9da5aa594383697e5b18892a7f95beb053f55","modified":1643251051660},{"_id":"themes/cactus/source/css/_highlight/xcode.styl","hash":"5e8532ae8366dcf6a4ef5e4813dc3d42ab3d0a50","modified":1643251051661},{"_id":"themes/cactus/source/css/_highlight/vs.styl","hash":"959a746f4b37aacb5d1d6ff1d57e0c045289d75d","modified":1643251051661},{"_id":"themes/cactus/source/lib/clipboard/clipboard.min.js","hash":"ee60ca5ba9401456105ef703a98092369b579c80","modified":1643251051664},{"_id":"themes/cactus/source/css/_highlight/zenburn.styl","hash":"68ff9332ccc03f9389b15b713415cde016f8088f","modified":1643251051661},{"_id":"themes/cactus/source/lib/vazir-font/Vazir-Black.woff","hash":"9e8d954c46eaad8b8234fa906e9a268ee354dced","modified":1643251051689},{"_id":"themes/cactus/source/lib/vazir-font/Vazir-Black.woff2","hash":"f1c5d7523d21c2bf820d827c9d5df4184c3866dc","modified":1643251051689},{"_id":"themes/cactus/source/lib/vazir-font/Vazir-Bold.woff2","hash":"8d4810991aa94f958aff20a9cd381caf338e3061","modified":1643251051691},{"_id":"themes/cactus/source/lib/vazir-font/Vazir-Bold.woff","hash":"9376516725e502f4375e06cc4fa7d940e2c93251","modified":1643251051690},{"_id":"themes/cactus/source/lib/vazir-font/Vazir-Light.woff","hash":"cba77d3d16f7565f9ea79bd7657f4e00c7fe851f","modified":1643251051692},{"_id":"themes/cactus/source/lib/vazir-font/Vazir-Light.woff2","hash":"e520d5f6bf7ea3c1e4f2aef2abbbc6a6f9b697cb","modified":1643251051692},{"_id":"themes/cactus/source/lib/vazir-font/Vazir-Medium.woff","hash":"2e8e3c873e6d98acc3c10aa84997104b276e1e68","modified":1643251051693},{"_id":"themes/cactus/source/lib/vazir-font/Vazir-Medium.woff2","hash":"976b7aa7c2c2c049c548a25b5914cfbda74b0453","modified":1643251051693},{"_id":"themes/cactus/source/lib/vazir-font/Vazir-Thin.woff","hash":"f231111b3c778b7a5898ea88c0f150c0e72be468","modified":1643251051694},{"_id":"themes/cactus/source/lib/vazir-font/Vazir-Thin.woff2","hash":"baa42f95b41411b9aeaa6c7594e5ccee10d42ac4","modified":1643251051694},{"_id":"themes/cactus/source/lib/vazir-font/Vazir.woff","hash":"c798391d624b9bb44497a87ffc4f7eb52042dceb","modified":1643251051695},{"_id":"themes/cactus/source/lib/vazir-font/Vazir.woff2","hash":"dc3c0ed67c9abb062b562e8553776f614d2946c2","modified":1643251051695},{"_id":"themes/cactus/source/css/_partial/post/actions_desktop.styl","hash":"dc726537928fc0d7703e73c0a5e4b82ad1731d59","modified":1643251051662},{"_id":"themes/cactus/source/css/_partial/post/actions_mobile.styl","hash":"0d2966c1d870392476864af8ee3ba312ba30cb82","modified":1643251051662},{"_id":"themes/cactus/source/lib/vazir-font/font-face.css","hash":"2a95709b15ee45fc2328051038ceedddf83235bb","modified":1643251051695},{"_id":"themes/cactus/source/lib/font-awesome/css/all.min.css","hash":"703603273f5d5d52eb456d6385e1a68294fbd568","modified":1643251051665},{"_id":"themes/cactus/source/lib/font-awesome/webfonts/fa-regular-400.eot","hash":"3ad44eb5c276d1435408f253ca78da729a1aca90","modified":1643251051669},{"_id":"themes/cactus/source/lib/font-awesome/webfonts/fa-regular-400.ttf","hash":"114f35e6d9053caca2ef6d1e34fea3f87a59245b","modified":1643251051670},{"_id":"themes/cactus/source/lib/font-awesome/webfonts/fa-regular-400.woff","hash":"f3f0ea4847825806062a9b7a0f629671eb6b6408","modified":1643251051670},{"_id":"themes/cactus/source/lib/font-awesome/webfonts/fa-regular-400.woff2","hash":"830f8653e5f4a5331ac0b47c5701f65fe9f1bb32","modified":1643251051670},{"_id":"themes/cactus/source/lib/justified-gallery/css/justifiedGallery.min.css","hash":"92bb6e468a1db7fbd99ccb960e15e28572254263","modified":1643251051675},{"_id":"source/_posts/koop-vector-tile/wa-boat-launch.png","hash":"e26bc88575e1fee0a2adf33446f9d3e598cc20f0","modified":1643251051592},{"_id":"themes/cactus/source/lib/justified-gallery/js/jquery.justifiedGallery.min.js","hash":"82ab395176c927ffbb2f7c95132ee0a06cd5d64a","modified":1643251051675},{"_id":"themes/cactus/source/lib/jquery/jquery.min.js","hash":"88523924351bac0b5d560fe0c5781e2556e7693d","modified":1643251051675},{"_id":"themes/cactus/source/lib/vazir-font/Vazir-Black.eot","hash":"603acd29416644e4b4fb8646abeada1865ba6869","modified":1643251051689},{"_id":"themes/cactus/source/lib/vazir-font/Vazir-Black.ttf","hash":"23ee4a19421de9a0ca9dddc5435a8efe5bf28d87","modified":1643251051689},{"_id":"themes/cactus/source/lib/vazir-font/Vazir-Light.ttf","hash":"b2372b67b9519fb4fa8e05de6c0ddae56845ff79","modified":1643251051691},{"_id":"themes/cactus/source/lib/vazir-font/Vazir-Bold.ttf","hash":"658c1da4f2a0124f6340058daa6873a86e6ba4fc","modified":1643251051690},{"_id":"themes/cactus/source/lib/vazir-font/Vazir-Bold.eot","hash":"9ef82b07f3adad7d644c9c3a6d35a0c727bd64e8","modified":1643251051690},{"_id":"themes/cactus/source/lib/vazir-font/Vazir-Medium.ttf","hash":"31cdbcc7215d01c9dd2beb8a28f8b7a7de75b9f4","modified":1643251051693},{"_id":"themes/cactus/source/lib/vazir-font/Vazir-Light.eot","hash":"32f51bf715663f5ca419e138617fc05f7055aef5","modified":1643251051691},{"_id":"themes/cactus/source/lib/vazir-font/Vazir-Thin.ttf","hash":"d53fcc2e2d6c9c77613afcd34058be1b75bb0fef","modified":1643251051694},{"_id":"themes/cactus/source/lib/vazir-font/Vazir-Medium.eot","hash":"17be9f699c30f0384004b46e991db8ac38a9992e","modified":1643251051692},{"_id":"themes/cactus/source/lib/vazir-font/Vazir.ttf","hash":"c7a3f2f4d56c4c4ec69d395baf39b55198da0254","modified":1643251051695},{"_id":"themes/cactus/source/lib/vazir-font/Vazir-Thin.eot","hash":"2c0ebb336dd012da8d575cae0ee4d048b65fe6e1","modified":1643251051693},{"_id":"themes/cactus/source/lib/vazir-font/Vazir.eot","hash":"bd3a7cb9eb70d36b4cfba8c5a05d234aefeefe3c","modified":1643251051695},{"_id":"themes/cactus/source/lib/font-awesome/webfonts/fa-brands-400.woff","hash":"91daac2bfba5e6a1a15ce44c53eab524d01c7fb0","modified":1643251051669},{"_id":"themes/cactus/source/lib/font-awesome/webfonts/fa-brands-400.woff2","hash":"f356aa2e4d9b7245985d312d3bfba180f774e3b7","modified":1643251051669},{"_id":"themes/cactus/source/lib/font-awesome/webfonts/fa-solid-900.woff","hash":"61f40daca6978e6e7ab761e748c2dd9d236c7586","modified":1643251051674},{"_id":"themes/cactus/source/lib/font-awesome/webfonts/fa-solid-900.woff2","hash":"b2c74520c3f506efbfefca867918e5ae28bd5222","modified":1643251051674},{"_id":"themes/cactus/source/lib/font-awesome/webfonts/fa-brands-400.eot","hash":"d4987ee41e0e4142d561f76b8ea8e034c4d5d9d2","modified":1643251051666},{"_id":"themes/cactus/source/lib/font-awesome/webfonts/fa-brands-400.ttf","hash":"f34ee061900ecd1ed3d3fd9f8f47f4e84c6d56bf","modified":1643251051668},{"_id":"themes/cactus/source/lib/font-awesome/webfonts/fa-regular-400.svg","hash":"1622937e49766e21eacf4ac7065b711f0fe580e1","modified":1643251051669},{"_id":"themes/cactus/source/lib/font-awesome/webfonts/fa-solid-900.ttf","hash":"91b40a8f284159d9fff81dc522670ef68d562682","modified":1643251051674},{"_id":"themes/cactus/source/lib/font-awesome/webfonts/fa-solid-900.eot","hash":"be6b63d528286b1be2328d871c9bae95d8d57174","modified":1643251051671},{"_id":"themes/cactus/source/images/logo.png","hash":"0e3029251dfda26adee2761f71377297e8c26871","modified":1643251051664},{"_id":"themes/cactus/source/lib/meslo-LG/MesloLGM-BoldItalic.ttf","hash":"b542b9591fbf33925d93f0695b6e123a9f0cfd43","modified":1643251051686},{"_id":"themes/cactus/source/lib/meslo-LG/MesloLGL-BoldItalic.ttf","hash":"b7d24ab1e4fad720f31a2b0cca1904ce1740d846","modified":1643251051679},{"_id":"themes/cactus/source/lib/meslo-LG/MesloLGS-BoldItalic.ttf","hash":"926035f0156cccf1b0ca507347f39bf9c510f51e","modified":1643251051687},{"_id":"themes/cactus/source/lib/meslo-LG/MesloLGL-Italic.ttf","hash":"9a23c6898b0943bd3d96c04df9a0f66e919451d8","modified":1643251051682},{"_id":"themes/cactus/source/lib/meslo-LG/MesloLGM-Italic.ttf","hash":"93ebc5098cf57a32b7b8d297681f31692c09bdfa","modified":1643251051687},{"_id":"themes/cactus/source/lib/meslo-LG/MesloLGS-Italic.ttf","hash":"9d757cc9f928fc83b2133283dd639c12b11d94ad","modified":1643251051688},{"_id":"source/_posts/mappreview/map-preview.png","hash":"6e2c951dba674b2fd5ce87de97327cbd8da8b90c","modified":1643251051594},{"_id":"themes/cactus/source/lib/meslo-LG/MesloLGL-Bold.ttf","hash":"34f7db59f1d023294e69976aa20b7d52b86165a4","modified":1643251051677},{"_id":"themes/cactus/source/lib/meslo-LG/MesloLGM-Bold.ttf","hash":"58be4b7760e9a84daa81929d046f9a15c4fd1c1a","modified":1643251051685},{"_id":"themes/cactus/source/lib/meslo-LG/MesloLGL-Regular.ttf","hash":"6c090d6bff3928fbf8a5f4104e58ed7f421aea7c","modified":1643251051684},{"_id":"themes/cactus/source/lib/meslo-LG/MesloLGM-Regular.ttf","hash":"20ce1fc7ae1254558ca044ae48283faaa58897e5","modified":1643251051687},{"_id":"themes/cactus/source/lib/meslo-LG/MesloLGS-Regular.ttf","hash":"de559f8d70d5b1ab2810597bfd0b1b9506f3ef01","modified":1643251051688},{"_id":"themes/cactus/source/lib/meslo-LG/MesloLGS-Bold.ttf","hash":"f9918fb93d6ab6850f5d38069a999c311af78816","modified":1643251051687},{"_id":"themes/cactus/source/lib/font-awesome/webfonts/fa-brands-400.svg","hash":"29e6c7e5a4d63d2c9563cd208456cb4f8a357868","modified":1643251051668},{"_id":"themes/cactus/source/lib/font-awesome/webfonts/fa-solid-900.svg","hash":"3a090431fdec61a25ed69b9e6f35a6656bde1595","modified":1643251051673},{"_id":"source/gallery/data/states.geojson","hash":"db3d3e7f0a3baf734156a5adedfd659f3e4b7dd8","modified":1643503033314},{"_id":"source/gallery/data/seattle-neighborhoods.geojson","hash":"711a6ce6dd1f94bfb33173eb50cc6b6ab27abe8b","modified":1643251051638},{"_id":"public/gallery/style/map.css","hash":"b4ba1c9cd3ae4971fe789adeb4c45751220db063","modified":1643503538318},{"_id":"public/gallery/src/covid-wa-county.js","hash":"1c054a2baab3d8812fe99428e680be68ab929a3c","modified":1643503538318},{"_id":"public/gallery/src/covid.js","hash":"743b6f98d4af2c0fb68672943b64b6cddef92e1b","modified":1643503538318},{"_id":"public/gallery/src/fl-straits.js","hash":"5c438500bd121eba8e40cb0103a616fd4c87d5da","modified":1643503538318},{"_id":"public/gallery/src/modernizr-custom.js","hash":"55cb4332f0a55c5496b96c6c4e773c00592bbe45","modified":1643503538318},{"_id":"public/gallery/src/seattle-json.js","hash":"bd59c15b08a2285a28c632263142f74472e37abc","modified":1643503538318},{"_id":"public/CNAME","hash":"54335a8781a698d4dc188a6543e17027fe147aad","modified":1643503538318},{"_id":"public/gallery/covid-states.html","hash":"ed4d36ee77d0cd38e211e2cd5b941c41fe3a09b8","modified":1643503538318},{"_id":"public/gallery/covid-wa-county.html","hash":"11bcefaab4f184041340db5e938e9d80f11c1986","modified":1643503538318},{"_id":"public/gallery/fl-straits.html","hash":"a4b1a6e659f79ee924cd6ab147331693e4f954c0","modified":1643503538318},{"_id":"public/gallery/index.html","hash":"20b90cde4230a5e15271e22d83d61572015c522f","modified":1643503538318},{"_id":"public/gallery/seattle-neighborhoods.html","hash":"8f06495eee214bc8acd73aad7280343ff0a9f71e","modified":1643503538318},{"_id":"public/uses/index.html","hash":"de9507e6a4517ce89476f81c2e50497237a90175","modified":1643503538318},{"_id":"public/archives/index.html","hash":"1fd59488569e0abc3afe78617897f469bbaedcd6","modified":1643503538318},{"_id":"public/archives/2019/index.html","hash":"4aff99ee7e60ab65956e4c4ce795174ddb0d8db7","modified":1643503538318},{"_id":"public/archives/2019/12/index.html","hash":"6eb9713914154b1f0afdec45438f5ba401d715f5","modified":1643503538318},{"_id":"public/archives/2019/05/index.html","hash":"f5989da13282a9193c20d8a47ab438d2ade4f11e","modified":1643503538318},{"_id":"public/archives/2020/index.html","hash":"10fc37365b2e2bf266a4257555b8a94f8a15be56","modified":1643503538318},{"_id":"public/archives/2020/03/index.html","hash":"70c61514334c28c0bac9f1e0d3ee94ec0505d1cf","modified":1643503538318},{"_id":"public/archives/2020/08/index.html","hash":"f9acb41ef92d035c975ab549546c42a6907d9407","modified":1643503538318},{"_id":"public/archives/2020/06/index.html","hash":"9f663fa0f48d232e827b9aff43193e2fc88c5e53","modified":1643503538318},{"_id":"public/archives/2021/index.html","hash":"1e9c8511ffc460c47b73c11538d456f0185b3346","modified":1643503538318},{"_id":"public/archives/2020/10/index.html","hash":"a37172dae14caddb2ce7a02e69e65b9a21a9996c","modified":1643503538318},{"_id":"public/archives/2021/06/index.html","hash":"4df96692cbd526433a2e0027b5233bbd8e8ac351","modified":1643503538318},{"_id":"public/index.html","hash":"e8b26d23ca7383bdce59d886cf5bb63367f3203a","modified":1643503538318},{"_id":"public/tags/python/index.html","hash":"c49288928437c668cfbc4632fc0045bcc1fcaec9","modified":1643503538318},{"_id":"public/tags/sublime-text/index.html","hash":"d314938fc81305b7f1e8592e693faa4c51bec14a","modified":1643503538318},{"_id":"public/tags/geojson/index.html","hash":"c9225617eba4614a68c1893887395534eb092d94","modified":1643503538318},{"_id":"public/tags/topojson/index.html","hash":"a6763c6a3a6e83e665a24625791deb7053f989a5","modified":1643503538318},{"_id":"public/tags/kotlin/index.html","hash":"6d8a2f27288961ead26a1ed68f2dbceff1e646e9","modified":1643503538318},{"_id":"public/tags/leaflet/index.html","hash":"e7ce7ba9795c4eb7b007a7c248cdb0f74f909626","modified":1643503538318},{"_id":"public/tags/multiplatform/index.html","hash":"ede449223a7aed51dd09a80e99708db111910589","modified":1643503538318},{"_id":"public/tags/esri/index.html","hash":"e7c24cd711f4589a49c30299169a90cbc7511832","modified":1643503538318},{"_id":"public/tags/arcgis-rest/index.html","hash":"8c0ed76600c13d264f9dfcd6f4be887fa38313b1","modified":1643503538318},{"_id":"public/tags/arcgis-runtime/index.html","hash":"5825f69e0e0bc9eaf5a2645686343a1afaf8c75a","modified":1643503538318},{"_id":"public/tags/google-maps/index.html","hash":"283471714ba4f7d4c4ada0bdbf958b4493529f10","modified":1643503538318},{"_id":"public/tags/geoservices/index.html","hash":"1c2fed41a2f05f9d92c6cfb5d37e4116e3aea715","modified":1643503538318},{"_id":"public/tags/arcgis/index.html","hash":"32cc86887206122838b72f10064473bbdb91c66b","modified":1643503538318},{"_id":"public/tags/postgis/index.html","hash":"35e7ccd3c229a142340a932aebd5b0c8fe7e56f7","modified":1643503538318},{"_id":"public/tags/vector-tiles/index.html","hash":"ff1c454a41df28aa84e71783891dd8f9077a9661","modified":1643503538318},{"_id":"public/tags/clikt/index.html","hash":"c4b928161eb594cabb758786ca99daa9291c5fca","modified":1643503538318},{"_id":"public/tags/coroutines/index.html","hash":"4aacdbaf77693982dbc8940c9e130962bc6803cd","modified":1643503538318},{"_id":"public/2021/06/19/kotlin-cli/index.html","hash":"fd82260d5170af1fb053a9b09d0c39b1fa2de6ec","modified":1643503538318},{"_id":"public/2021/06/13/koop-vector-tile/index.html","hash":"7a3fd04129a1db79486825e044f05178afe058b5","modified":1643503538318},{"_id":"public/2020/10/04/pgkoop/index.html","hash":"07042e14b42b88ac31287403771c41d5585a4a1b","modified":1643503538318},{"_id":"public/2020/08/14/mappreview/index.html","hash":"40c35505c70c7fd96b9775b51f5951f954f3709f","modified":1643503538318},{"_id":"public/2020/06/28/sqldelight/index.html","hash":"afe3b75fc4879e2cb0a09f31b2d6a0012e52ae6f","modified":1643503538318},{"_id":"public/2020/03/31/jq-geojson/index.html","hash":"06e773d08a40ebc0b4cd23d6c4a5ea41c3d7fb50","modified":1643503538318},{"_id":"public/2019/12/06/kotlin-mulitplatform/index.html","hash":"4f835fcd7f27b5a0ac64c42f968b7eb3dbf8e4d3","modified":1643503538318},{"_id":"public/2019/05/24/spatial-references/index.html","hash":"907ee4d719050e891038c95c58d22ed3405feec5","modified":1643503538318},{"_id":"public/images/favicon.ico","hash":"189f9842bcb79a6f8f9e8445bc8bbd773443826b","modified":1643503538318},{"_id":"public/images/apple-touch-icon.png","hash":"57e2def34682655f41a0be2d083f16765ba7858b","modified":1643503538318},{"_id":"public/images/favicon-192x192.png","hash":"96e6fcbbb13a5914a6131391e210eb7dfd13d692","modified":1643503538318},{"_id":"public/lib/vazir-font/Vazir-Black.woff2","hash":"f1c5d7523d21c2bf820d827c9d5df4184c3866dc","modified":1643503538318},{"_id":"public/lib/vazir-font/Vazir-Bold.woff2","hash":"8d4810991aa94f958aff20a9cd381caf338e3061","modified":1643503538318},{"_id":"public/lib/vazir-font/Vazir-Black.woff","hash":"9e8d954c46eaad8b8234fa906e9a268ee354dced","modified":1643503538318},{"_id":"public/lib/vazir-font/Vazir-Bold.woff","hash":"9376516725e502f4375e06cc4fa7d940e2c93251","modified":1643503538318},{"_id":"public/lib/vazir-font/Vazir-Light.woff2","hash":"e520d5f6bf7ea3c1e4f2aef2abbbc6a6f9b697cb","modified":1643503538318},{"_id":"public/lib/vazir-font/Vazir-Light.woff","hash":"cba77d3d16f7565f9ea79bd7657f4e00c7fe851f","modified":1643503538318},{"_id":"public/lib/vazir-font/Vazir-Medium.woff","hash":"2e8e3c873e6d98acc3c10aa84997104b276e1e68","modified":1643503538318},{"_id":"public/lib/vazir-font/Vazir-Medium.woff2","hash":"976b7aa7c2c2c049c548a25b5914cfbda74b0453","modified":1643503538318},{"_id":"public/lib/vazir-font/Vazir-Thin.woff2","hash":"baa42f95b41411b9aeaa6c7594e5ccee10d42ac4","modified":1643503538318},{"_id":"public/lib/vazir-font/Vazir-Thin.woff","hash":"f231111b3c778b7a5898ea88c0f150c0e72be468","modified":1643503538318},{"_id":"public/lib/vazir-font/Vazir.woff2","hash":"dc3c0ed67c9abb062b562e8553776f614d2946c2","modified":1643503538318},{"_id":"public/lib/vazir-font/Vazir.woff","hash":"c798391d624b9bb44497a87ffc4f7eb52042dceb","modified":1643503538318},{"_id":"public/lib/font-awesome/webfonts/fa-regular-400.ttf","hash":"114f35e6d9053caca2ef6d1e34fea3f87a59245b","modified":1643503538318},{"_id":"public/lib/font-awesome/webfonts/fa-regular-400.woff","hash":"f3f0ea4847825806062a9b7a0f629671eb6b6408","modified":1643503538318},{"_id":"public/lib/font-awesome/webfonts/fa-regular-400.woff2","hash":"830f8653e5f4a5331ac0b47c5701f65fe9f1bb32","modified":1643503538318},{"_id":"public/lib/font-awesome/webfonts/fa-regular-400.eot","hash":"3ad44eb5c276d1435408f253ca78da729a1aca90","modified":1643503538318},{"_id":"public/gallery/data/counties-wa-point.geojson","hash":"7b3e17138a630de995b87044149852ca39ec4c3e","modified":1643503538318},{"_id":"public/2021/06/19/kotlin-cli/app-structure.png","hash":"b09168e1cf41d798eceefc2db349c7602a17dd08","modified":1643503538318},{"_id":"public/lib/vazir-font/Vazir-Black.eot","hash":"603acd29416644e4b4fb8646abeada1865ba6869","modified":1643503538318},{"_id":"public/lib/vazir-font/Vazir-Black.ttf","hash":"23ee4a19421de9a0ca9dddc5435a8efe5bf28d87","modified":1643503538318},{"_id":"public/lib/vazir-font/Vazir-Bold.eot","hash":"9ef82b07f3adad7d644c9c3a6d35a0c727bd64e8","modified":1643503538318},{"_id":"public/lib/vazir-font/Vazir-Bold.ttf","hash":"658c1da4f2a0124f6340058daa6873a86e6ba4fc","modified":1643503538318},{"_id":"public/lib/vazir-font/Vazir-Light.eot","hash":"32f51bf715663f5ca419e138617fc05f7055aef5","modified":1643503538318},{"_id":"public/lib/vazir-font/Vazir-Light.ttf","hash":"b2372b67b9519fb4fa8e05de6c0ddae56845ff79","modified":1643503538318},{"_id":"public/lib/vazir-font/Vazir-Medium.ttf","hash":"31cdbcc7215d01c9dd2beb8a28f8b7a7de75b9f4","modified":1643503538318},{"_id":"public/lib/vazir-font/Vazir-Thin.ttf","hash":"d53fcc2e2d6c9c77613afcd34058be1b75bb0fef","modified":1643503538318},{"_id":"public/lib/vazir-font/Vazir-Thin.eot","hash":"2c0ebb336dd012da8d575cae0ee4d048b65fe6e1","modified":1643503538318},{"_id":"public/lib/vazir-font/Vazir-Medium.eot","hash":"17be9f699c30f0384004b46e991db8ac38a9992e","modified":1643503538318},{"_id":"public/lib/vazir-font/Vazir.eot","hash":"bd3a7cb9eb70d36b4cfba8c5a05d234aefeefe3c","modified":1643503538318},{"_id":"public/lib/vazir-font/Vazir.ttf","hash":"c7a3f2f4d56c4c4ec69d395baf39b55198da0254","modified":1643503538318},{"_id":"public/lib/font-awesome/webfonts/fa-brands-400.woff","hash":"91daac2bfba5e6a1a15ce44c53eab524d01c7fb0","modified":1643503538318},{"_id":"public/lib/font-awesome/webfonts/fa-brands-400.woff2","hash":"f356aa2e4d9b7245985d312d3bfba180f774e3b7","modified":1643503538318},{"_id":"public/lib/font-awesome/webfonts/fa-solid-900.woff","hash":"61f40daca6978e6e7ab761e748c2dd9d236c7586","modified":1643503538318},{"_id":"public/lib/font-awesome/webfonts/fa-solid-900.woff2","hash":"b2c74520c3f506efbfefca867918e5ae28bd5222","modified":1643503538318},{"_id":"public/css/rtl.css","hash":"9589fac02a34fd9084f805f801889028756bbb65","modified":1643503538318},{"_id":"public/js/search.js","hash":"a74d0c601f820160825a2e4ad13618074d714933","modified":1643503538318},{"_id":"public/js/main.js","hash":"584c5a69ac81a483a1c4377a2e2cf326c2795e7b","modified":1643503538318},{"_id":"public/lib/vazir-font/font-face.css","hash":"2a95709b15ee45fc2328051038ceedddf83235bb","modified":1643503538318},{"_id":"public/lib/clipboard/clipboard.min.js","hash":"ee60ca5ba9401456105ef703a98092369b579c80","modified":1643503538318},{"_id":"public/lib/justified-gallery/css/justifiedGallery.min.css","hash":"92bb6e468a1db7fbd99ccb960e15e28572254263","modified":1643503538318},{"_id":"public/css/style.css","hash":"86df9d7b0633483b86f1060e4f56e1f964ffe4f5","modified":1643503538318},{"_id":"public/lib/jquery/jquery.min.js","hash":"88523924351bac0b5d560fe0c5781e2556e7693d","modified":1643503538318},{"_id":"public/lib/justified-gallery/js/jquery.justifiedGallery.min.js","hash":"82ab395176c927ffbb2f7c95132ee0a06cd5d64a","modified":1643503538318},{"_id":"public/lib/font-awesome/css/all.min.css","hash":"703603273f5d5d52eb456d6385e1a68294fbd568","modified":1643503538318},{"_id":"public/lib/font-awesome/webfonts/fa-brands-400.eot","hash":"d4987ee41e0e4142d561f76b8ea8e034c4d5d9d2","modified":1643503538318},{"_id":"public/lib/font-awesome/webfonts/fa-brands-400.ttf","hash":"f34ee061900ecd1ed3d3fd9f8f47f4e84c6d56bf","modified":1643503538318},{"_id":"public/lib/font-awesome/webfonts/fa-regular-400.svg","hash":"1622937e49766e21eacf4ac7065b711f0fe580e1","modified":1643503538318},{"_id":"public/lib/font-awesome/webfonts/fa-solid-900.ttf","hash":"91b40a8f284159d9fff81dc522670ef68d562682","modified":1643503538318},{"_id":"public/lib/font-awesome/webfonts/fa-solid-900.eot","hash":"be6b63d528286b1be2328d871c9bae95d8d57174","modified":1643503538318},{"_id":"public/images/logo.png","hash":"0e3029251dfda26adee2761f71377297e8c26871","modified":1643503538318},{"_id":"public/gallery/data/counties-wa.geojson","hash":"e8fafe8f48727fb93c31e3fddb90e53299fc43b5","modified":1643503538318},{"_id":"public/2020/10/04/pgkoop/agol.png","hash":"246ba3ca68d5c664a0839a31fac7579c72b12198","modified":1643503538318},{"_id":"public/lib/meslo-LG/MesloLGL-BoldItalic.ttf","hash":"b7d24ab1e4fad720f31a2b0cca1904ce1740d846","modified":1643503538318},{"_id":"public/lib/meslo-LG/MesloLGM-BoldItalic.ttf","hash":"b542b9591fbf33925d93f0695b6e123a9f0cfd43","modified":1643503538318},{"_id":"public/lib/meslo-LG/MesloLGS-BoldItalic.ttf","hash":"926035f0156cccf1b0ca507347f39bf9c510f51e","modified":1643503538318},{"_id":"public/2020/10/04/pgkoop/qgis.png","hash":"4247c5fe3fc30d93f8492a87bb8f6c93489d86f4","modified":1643503538318},{"_id":"public/2020/10/04/pgkoop/leaflet.png","hash":"2120bffbeb70e404622b6c7d9d4b54573f854743","modified":1643503538318},{"_id":"public/lib/meslo-LG/MesloLGL-Italic.ttf","hash":"9a23c6898b0943bd3d96c04df9a0f66e919451d8","modified":1643503538318},{"_id":"public/lib/meslo-LG/MesloLGM-Italic.ttf","hash":"93ebc5098cf57a32b7b8d297681f31692c09bdfa","modified":1643503538318},{"_id":"public/lib/meslo-LG/MesloLGS-Italic.ttf","hash":"9d757cc9f928fc83b2133283dd639c12b11d94ad","modified":1643503538318},{"_id":"public/lib/meslo-LG/MesloLGL-Regular.ttf","hash":"6c090d6bff3928fbf8a5f4104e58ed7f421aea7c","modified":1643503538318},{"_id":"public/lib/meslo-LG/MesloLGM-Bold.ttf","hash":"58be4b7760e9a84daa81929d046f9a15c4fd1c1a","modified":1643503538318},{"_id":"public/lib/meslo-LG/MesloLGL-Bold.ttf","hash":"34f7db59f1d023294e69976aa20b7d52b86165a4","modified":1643503538318},{"_id":"public/lib/meslo-LG/MesloLGS-Regular.ttf","hash":"de559f8d70d5b1ab2810597bfd0b1b9506f3ef01","modified":1643503538318},{"_id":"public/lib/meslo-LG/MesloLGM-Regular.ttf","hash":"20ce1fc7ae1254558ca044ae48283faaa58897e5","modified":1643503538318},{"_id":"public/lib/meslo-LG/MesloLGS-Bold.ttf","hash":"f9918fb93d6ab6850f5d38069a999c311af78816","modified":1643503538318},{"_id":"public/2021/06/13/koop-vector-tile/wa-boat-launch.png","hash":"e26bc88575e1fee0a2adf33446f9d3e598cc20f0","modified":1643503538318},{"_id":"public/lib/font-awesome/webfonts/fa-brands-400.svg","hash":"29e6c7e5a4d63d2c9563cd208456cb4f8a357868","modified":1643503538318},{"_id":"public/lib/font-awesome/webfonts/fa-solid-900.svg","hash":"3a090431fdec61a25ed69b9e6f35a6656bde1595","modified":1643503538318},{"_id":"public/2020/08/14/mappreview/map-preview.png","hash":"6e2c951dba674b2fd5ce87de97327cbd8da8b90c","modified":1643503538318},{"_id":"public/gallery/data/states.geojson","hash":"db3d3e7f0a3baf734156a5adedfd659f3e4b7dd8","modified":1643503538318},{"_id":"public/gallery/data/seattle-neighborhoods.geojson","hash":"711a6ce6dd1f94bfb33173eb50cc6b6ab27abe8b","modified":1643503538318}],"Category":[],"Data":[{"_id":"projects","data":[{"name":"koop-provider-pg","url":"https://www.npmjs.com/package/koop-provider-pg","desc":"Koop provider to query PostGIS spatial data"},{"name":"Map Preview","url":"https://packagecontrol.io/packages/MapPreview","desc":"Preview map layer files in your web browser using Sublime Text 3"},{"name":"kotlin mulitplatform template","url":"https://github.com/doneill/kotlin-multiplatform-template","desc":"Kotlin MPP template to bootstrap Mobile/Web"}]}],"Page":[{"_content":"<div id=\"mapid\"></div>\n<link rel=\"stylesheet\" href=\"style/map.css\" type=\"text/css\">\n<style>\n .info { padding: 6px 8px; font: 12px/14px Arial, Helvetica, sans-serif; background: white; background: rgba(255,255,255,0.8); box-shadow: 0 0 15px rgba(0,0,0,0.2); border-radius: 5px; } .info h4 { margin: 0 0 5px; color: #777; }\n .legend { text-align: left; line-height: 18px; color: #555; } .legend i { width: 18px; height: 18px; float: left; margin-right: 8px; opacity: 0.7; }\n .attr { padding: 6px 8px; text-align: right; font: 12px/14px Arial, Helvetica, sans-serif;}\n</style>\n<script src=\"src/covid.js\"></script>\n\n<div class=\"attr\">Coronavirus data updates <a href=\"https://gist.github.com/doneill/0e841403ff362ce49ab83737cbb637ae\">source code</a></div>","source":"gallery/covid-states.html","raw":"<div id=\"mapid\"></div>\n<link rel=\"stylesheet\" href=\"style/map.css\" type=\"text/css\">\n<style>\n .info { padding: 6px 8px; font: 12px/14px Arial, Helvetica, sans-serif; background: white; background: rgba(255,255,255,0.8); box-shadow: 0 0 15px rgba(0,0,0,0.2); border-radius: 5px; } .info h4 { margin: 0 0 5px; color: #777; }\n .legend { text-align: left; line-height: 18px; color: #555; } .legend i { width: 18px; height: 18px; float: left; margin-right: 8px; opacity: 0.7; }\n .attr { padding: 6px 8px; text-align: right; font: 12px/14px Arial, Helvetica, sans-serif;}\n</style>\n<script src=\"src/covid.js\"></script>\n\n<div class=\"attr\">Coronavirus data updates <a href=\"https://gist.github.com/doneill/0e841403ff362ce49ab83737cbb637ae\">source code</a></div>","date":"2022-01-27T02:37:31.596Z","updated":"2022-01-27T02:37:31.596Z","path":"gallery/covid-states.html","title":"","comments":1,"layout":"page","_id":"ckz0jkmcs0000nvhwfx7ghixz","content":"<div id=\"mapid\"></div>\n<link rel=\"stylesheet\" href=\"style/map.css\" type=\"text/css\">\n<style>\n .info { padding: 6px 8px; font: 12px/14px Arial, Helvetica, sans-serif; background: white; background: rgba(255,255,255,0.8); box-shadow: 0 0 15px rgba(0,0,0,0.2); border-radius: 5px; } .info h4 { margin: 0 0 5px; color: #777; }\n .legend { text-align: left; line-height: 18px; color: #555; } .legend i { width: 18px; height: 18px; float: left; margin-right: 8px; opacity: 0.7; }\n .attr { padding: 6px 8px; text-align: right; font: 12px/14px Arial, Helvetica, sans-serif;}\n</style>\n<script src=\"src/covid.js\"></script>\n\n<div class=\"attr\">Coronavirus data updates <a href=\"https://gist.github.com/doneill/0e841403ff362ce49ab83737cbb637ae\">source code</a></div>","site":{"data":{"projects":[{"name":"koop-provider-pg","url":"https://www.npmjs.com/package/koop-provider-pg","desc":"Koop provider to query PostGIS spatial data"},{"name":"Map Preview","url":"https://packagecontrol.io/packages/MapPreview","desc":"Preview map layer files in your web browser using Sublime Text 3"},{"name":"kotlin mulitplatform template","url":"https://github.com/doneill/kotlin-multiplatform-template","desc":"Kotlin MPP template to bootstrap Mobile/Web"}]}},"excerpt":"","more":"<div id=\"mapid\"></div>\n<link rel=\"stylesheet\" href=\"style/map.css\" type=\"text/css\">\n<style>\n .info { padding: 6px 8px; font: 12px/14px Arial, Helvetica, sans-serif; background: white; background: rgba(255,255,255,0.8); box-shadow: 0 0 15px rgba(0,0,0,0.2); border-radius: 5px; } .info h4 { margin: 0 0 5px; color: #777; }\n .legend { text-align: left; line-height: 18px; color: #555; } .legend i { width: 18px; height: 18px; float: left; margin-right: 8px; opacity: 0.7; }\n .attr { padding: 6px 8px; text-align: right; font: 12px/14px Arial, Helvetica, sans-serif;}\n</style>\n<script src=\"src/covid.js\"></script>\n\n<div class=\"attr\">Coronavirus data updates <a href=\"https://gist.github.com/doneill/0e841403ff362ce49ab83737cbb637ae\">source code</a></div>"},{"_content":"<div id=\"mapid\"></div>\n<link rel=\"stylesheet\" href=\"style/map.css\" type=\"text/css\">\n<style>\n .info { padding: 6px 8px; font: 12px/14px Arial, Helvetica, sans-serif; background: white; background: rgba(255,255,255,0.8); box-shadow: 0 0 15px rgba(0,0,0,0.2); border-radius: 5px; } .info h4 { margin: 0 0 5px; color: #777; }\n .legend { text-align: left; line-height: 18px; color: #555; } .legend i { width: 18px; height: 18px; float: left; margin-right: 8px; opacity: 0.7; }\n .attr { padding: 6px 8px; text-align: right; font: 12px/14px Arial, Helvetica, sans-serif;}\n</style>\n<script src=\"src/covid-wa-county.js\"></script>","source":"gallery/covid-wa-county.html","raw":"<div id=\"mapid\"></div>\n<link rel=\"stylesheet\" href=\"style/map.css\" type=\"text/css\">\n<style>\n .info { padding: 6px 8px; font: 12px/14px Arial, Helvetica, sans-serif; background: white; background: rgba(255,255,255,0.8); box-shadow: 0 0 15px rgba(0,0,0,0.2); border-radius: 5px; } .info h4 { margin: 0 0 5px; color: #777; }\n .legend { text-align: left; line-height: 18px; color: #555; } .legend i { width: 18px; height: 18px; float: left; margin-right: 8px; opacity: 0.7; }\n .attr { padding: 6px 8px; text-align: right; font: 12px/14px Arial, Helvetica, sans-serif;}\n</style>\n<script src=\"src/covid-wa-county.js\"></script>","date":"2022-01-27T02:37:31.596Z","updated":"2022-01-27T02:37:31.596Z","path":"gallery/covid-wa-county.html","title":"","comments":1,"layout":"page","_id":"ckz0jkmct0001nvhwatz75prk","content":"<div id=\"mapid\"></div>\n<link rel=\"stylesheet\" href=\"style/map.css\" type=\"text/css\">\n<style>\n .info { padding: 6px 8px; font: 12px/14px Arial, Helvetica, sans-serif; background: white; background: rgba(255,255,255,0.8); box-shadow: 0 0 15px rgba(0,0,0,0.2); border-radius: 5px; } .info h4 { margin: 0 0 5px; color: #777; }\n .legend { text-align: left; line-height: 18px; color: #555; } .legend i { width: 18px; height: 18px; float: left; margin-right: 8px; opacity: 0.7; }\n .attr { padding: 6px 8px; text-align: right; font: 12px/14px Arial, Helvetica, sans-serif;}\n</style>\n<script src=\"src/covid-wa-county.js\"></script>","site":{"data":{"projects":[{"name":"koop-provider-pg","url":"https://www.npmjs.com/package/koop-provider-pg","desc":"Koop provider to query PostGIS spatial data"},{"name":"Map Preview","url":"https://packagecontrol.io/packages/MapPreview","desc":"Preview map layer files in your web browser using Sublime Text 3"},{"name":"kotlin mulitplatform template","url":"https://github.com/doneill/kotlin-multiplatform-template","desc":"Kotlin MPP template to bootstrap Mobile/Web"}]}},"excerpt":"","more":"<div id=\"mapid\"></div>\n<link rel=\"stylesheet\" href=\"style/map.css\" type=\"text/css\">\n<style>\n .info { padding: 6px 8px; font: 12px/14px Arial, Helvetica, sans-serif; background: white; background: rgba(255,255,255,0.8); box-shadow: 0 0 15px rgba(0,0,0,0.2); border-radius: 5px; } .info h4 { margin: 0 0 5px; color: #777; }\n .legend { text-align: left; line-height: 18px; color: #555; } .legend i { width: 18px; height: 18px; float: left; margin-right: 8px; opacity: 0.7; }\n .attr { padding: 6px 8px; text-align: right; font: 12px/14px Arial, Helvetica, sans-serif;}\n</style>\n<script src=\"src/covid-wa-county.js\"></script>"},{"_content":"<!DOCTYPE html>\n<html>\n <head>\n <meta charset=\"utf-8\">\n <title>OpenLayers Demo</title>\n <style>\n #map .ol-zoom .ol-zoom-out {\n margin-top: 204px;\n }\n #map .ol-zoomslider {\n background-color: transparent;\n top: 2.3em;\n }\n\n #map .ol-touch .ol-zoom .ol-zoom-out {\n margin-top: 212px;\n }\n #map .ol-touch .ol-zoomslider {\n top: 2.75em;\n }\n\n #map .ol-zoom-in.ol-has-tooltip:hover [role=tooltip],\n #map .ol-zoom-in.ol-has-tooltip:focus [role=tooltip] {\n top: 3px;\n }\n\n #map .ol-zoom-out.ol-has-tooltip:hover [role=tooltip],\n #map .ol-zoom-out.ol-has-tooltip:focus [role=tooltip] {\n top: 232px;\n }\n\n .map:-moz-full-screen {\n height: 100%;\n }\n .map:-webkit-full-screen {\n height: 100%;\n }\n .map:-ms-fullscreen {\n height: 100%;\n }\n .map:fullscreen {\n height: 100%;\n }\n </style>\n\n <link rel=\"stylesheet\" href=\"https://cdn.rawgit.com/openlayers/openlayers.github.io/master/en/v5.3.0/css/ol.css\" type=\"text/css\">\n <script src=\"https://cdn.rawgit.com/openlayers/openlayers.github.io/master/en/v5.3.0/build/ol.js\"></script>\n </head>\n <body>\n <div id=\"map\"></div>\n <script src=\"src/fl-straits.js\"></script>\n </body>\n</html>","source":"gallery/fl-straits.html","raw":"<!DOCTYPE html>\n<html>\n <head>\n <meta charset=\"utf-8\">\n <title>OpenLayers Demo</title>\n <style>\n #map .ol-zoom .ol-zoom-out {\n margin-top: 204px;\n }\n #map .ol-zoomslider {\n background-color: transparent;\n top: 2.3em;\n }\n\n #map .ol-touch .ol-zoom .ol-zoom-out {\n margin-top: 212px;\n }\n #map .ol-touch .ol-zoomslider {\n top: 2.75em;\n }\n\n #map .ol-zoom-in.ol-has-tooltip:hover [role=tooltip],\n #map .ol-zoom-in.ol-has-tooltip:focus [role=tooltip] {\n top: 3px;\n }\n\n #map .ol-zoom-out.ol-has-tooltip:hover [role=tooltip],\n #map .ol-zoom-out.ol-has-tooltip:focus [role=tooltip] {\n top: 232px;\n }\n\n .map:-moz-full-screen {\n height: 100%;\n }\n .map:-webkit-full-screen {\n height: 100%;\n }\n .map:-ms-fullscreen {\n height: 100%;\n }\n .map:fullscreen {\n height: 100%;\n }\n </style>\n\n <link rel=\"stylesheet\" href=\"https://cdn.rawgit.com/openlayers/openlayers.github.io/master/en/v5.3.0/css/ol.css\" type=\"text/css\">\n <script src=\"https://cdn.rawgit.com/openlayers/openlayers.github.io/master/en/v5.3.0/build/ol.js\"></script>\n </head>\n <body>\n <div id=\"map\"></div>\n <script src=\"src/fl-straits.js\"></script>\n </body>\n</html>","date":"2022-01-27T02:37:31.653Z","updated":"2022-01-27T02:37:31.653Z","path":"gallery/fl-straits.html","title":"","comments":1,"layout":"page","_id":"ckz0jkmcu0002nvhwhhajaeb7","content":"<!DOCTYPE html>\n<html>\n <head>\n <meta charset=\"utf-8\">\n <title>OpenLayers Demo</title>\n <style>\n #map .ol-zoom .ol-zoom-out {\n margin-top: 204px;\n }\n #map .ol-zoomslider {\n background-color: transparent;\n top: 2.3em;\n }\n\n #map .ol-touch .ol-zoom .ol-zoom-out {\n margin-top: 212px;\n }\n #map .ol-touch .ol-zoomslider {\n top: 2.75em;\n }\n\n #map .ol-zoom-in.ol-has-tooltip:hover [role=tooltip],\n #map .ol-zoom-in.ol-has-tooltip:focus [role=tooltip] {\n top: 3px;\n }\n\n #map .ol-zoom-out.ol-has-tooltip:hover [role=tooltip],\n #map .ol-zoom-out.ol-has-tooltip:focus [role=tooltip] {\n top: 232px;\n }\n\n .map:-moz-full-screen {\n height: 100%;\n }\n .map:-webkit-full-screen {\n height: 100%;\n }\n .map:-ms-fullscreen {\n height: 100%;\n }\n .map:fullscreen {\n height: 100%;\n }\n </style>\n\n <link rel=\"stylesheet\" href=\"https://cdn.rawgit.com/openlayers/openlayers.github.io/master/en/v5.3.0/css/ol.css\" type=\"text/css\">\n <script src=\"https://cdn.rawgit.com/openlayers/openlayers.github.io/master/en/v5.3.0/build/ol.js\"></script>\n </head>\n <body>\n <div id=\"map\"></div>\n <script src=\"src/fl-straits.js\"></script>\n </body>\n</html>","site":{"data":{"projects":[{"name":"koop-provider-pg","url":"https://www.npmjs.com/package/koop-provider-pg","desc":"Koop provider to query PostGIS spatial data"},{"name":"Map Preview","url":"https://packagecontrol.io/packages/MapPreview","desc":"Preview map layer files in your web browser using Sublime Text 3"},{"name":"kotlin mulitplatform template","url":"https://github.com/doneill/kotlin-multiplatform-template","desc":"Kotlin MPP template to bootstrap Mobile/Web"}]}},"excerpt":"","more":"<!DOCTYPE html>\n<html>\n <head>\n <meta charset=\"utf-8\">\n <title>OpenLayers Demo</title>\n <style>\n #map .ol-zoom .ol-zoom-out {\n margin-top: 204px;\n }\n #map .ol-zoomslider {\n background-color: transparent;\n top: 2.3em;\n }\n\n #map .ol-touch .ol-zoom .ol-zoom-out {\n margin-top: 212px;\n }\n #map .ol-touch .ol-zoomslider {\n top: 2.75em;\n }\n\n #map .ol-zoom-in.ol-has-tooltip:hover [role=tooltip],\n #map .ol-zoom-in.ol-has-tooltip:focus [role=tooltip] {\n top: 3px;\n }\n\n #map .ol-zoom-out.ol-has-tooltip:hover [role=tooltip],\n #map .ol-zoom-out.ol-has-tooltip:focus [role=tooltip] {\n top: 232px;\n }\n\n .map:-moz-full-screen {\n height: 100%;\n }\n .map:-webkit-full-screen {\n height: 100%;\n }\n .map:-ms-fullscreen {\n height: 100%;\n }\n .map:fullscreen {\n height: 100%;\n }\n </style>\n\n <link rel=\"stylesheet\" href=\"https://cdn.rawgit.com/openlayers/openlayers.github.io/master/en/v5.3.0/css/ol.css\" type=\"text/css\">\n <script src=\"https://cdn.rawgit.com/openlayers/openlayers.github.io/master/en/v5.3.0/build/ol.js\"></script>\n </head>\n <body>\n <div id=\"map\"></div>\n <script src=\"src/fl-straits.js\"></script>\n </body>\n</html>"},{"title":"gallery","date":"2019-12-28T01:42:43.000Z","_content":"\n## Map Plugins\n- [Map Preview](https://github.com/doneill/MapPreview)\n Preview map layer files in Sublime Text 3\n\n## Web Maps\n\n- [Florida Straits](fl-straits.html)\n Straits of Florida and Approaches nautical chart\n\n- [Seattle Neighborhoods](seattle-neighborhoods.html)\n GeoJSON polygons representing Seattle neighborhoods\n\n- [Covid-19 States](covid-states.html)\n GeoJSON polygons representing covid-19 stats\n\n- [Washington State County Level Covid-19 Stats](covid-wa-county.html)\n GeoJSON polygons representing covid-19 stats\n","source":"gallery/index.md","raw":"---\ntitle: gallery\ndate: 2019-12-27 17:42:43\n---\n\n## Map Plugins\n- [Map Preview](https://github.com/doneill/MapPreview)\n Preview map layer files in Sublime Text 3\n\n## Web Maps\n\n- [Florida Straits](fl-straits.html)\n Straits of Florida and Approaches nautical chart\n\n- [Seattle Neighborhoods](seattle-neighborhoods.html)\n GeoJSON polygons representing Seattle neighborhoods\n\n- [Covid-19 States](covid-states.html)\n GeoJSON polygons representing covid-19 stats\n\n- [Washington State County Level Covid-19 Stats](covid-wa-county.html)\n GeoJSON polygons representing covid-19 stats\n","updated":"2019-12-28T01:42:43.000Z","path":"gallery/index.html","comments":1,"layout":"page","_id":"ckz0jkmcu0003nvhw1soa1nrr","content":"<h2 id=\"Map-Plugins\"><a href=\"#Map-Plugins\" class=\"headerlink\" title=\"Map Plugins\"></a>Map Plugins</h2><ul>\n<li><a href=\"https://github.com/doneill/MapPreview\">Map Preview</a><br> Preview map layer files in Sublime Text 3</li>\n</ul>\n<h2 id=\"Web-Maps\"><a href=\"#Web-Maps\" class=\"headerlink\" title=\"Web Maps\"></a>Web Maps</h2><ul>\n<li><p><a href=\"fl-straits.html\">Florida Straits</a><br> Straits of Florida and Approaches nautical chart</p>\n</li>\n<li><p><a href=\"seattle-neighborhoods.html\">Seattle Neighborhoods</a><br> GeoJSON polygons representing Seattle neighborhoods</p>\n</li>\n<li><p><a href=\"covid-states.html\">Covid-19 States</a><br> GeoJSON polygons representing covid-19 stats</p>\n</li>\n<li><p><a href=\"covid-wa-county.html\">Washington State County Level Covid-19 Stats</a><br> GeoJSON polygons representing covid-19 stats</p>\n</li>\n</ul>\n","site":{"data":{"projects":[{"name":"koop-provider-pg","url":"https://www.npmjs.com/package/koop-provider-pg","desc":"Koop provider to query PostGIS spatial data"},{"name":"Map Preview","url":"https://packagecontrol.io/packages/MapPreview","desc":"Preview map layer files in your web browser using Sublime Text 3"},{"name":"kotlin mulitplatform template","url":"https://github.com/doneill/kotlin-multiplatform-template","desc":"Kotlin MPP template to bootstrap Mobile/Web"}]}},"excerpt":"","more":"<h2 id=\"Map-Plugins\"><a href=\"#Map-Plugins\" class=\"headerlink\" title=\"Map Plugins\"></a>Map Plugins</h2><ul>\n<li><a href=\"https://github.com/doneill/MapPreview\">Map Preview</a><br> Preview map layer files in Sublime Text 3</li>\n</ul>\n<h2 id=\"Web-Maps\"><a href=\"#Web-Maps\" class=\"headerlink\" title=\"Web Maps\"></a>Web Maps</h2><ul>\n<li><p><a href=\"fl-straits.html\">Florida Straits</a><br> Straits of Florida and Approaches nautical chart</p>\n</li>\n<li><p><a href=\"seattle-neighborhoods.html\">Seattle Neighborhoods</a><br> GeoJSON polygons representing Seattle neighborhoods</p>\n</li>\n<li><p><a href=\"covid-states.html\">Covid-19 States</a><br> GeoJSON polygons representing covid-19 stats</p>\n</li>\n<li><p><a href=\"covid-wa-county.html\">Washington State County Level Covid-19 Stats</a><br> GeoJSON polygons representing covid-19 stats</p>\n</li>\n</ul>\n"},{"_content":"<div id=\"mapid\"></div>\n<link rel=\"stylesheet\" href=\"style/map.css\" type=\"text/css\">\n<script src=\"src/seattle-json.js\"></script>\n\n","source":"gallery/seattle-neighborhoods.html","raw":"<div id=\"mapid\"></div>\n<link rel=\"stylesheet\" href=\"style/map.css\" type=\"text/css\">\n<script src=\"src/seattle-json.js\"></script>\n\n","date":"2022-01-27T02:37:31.653Z","updated":"2022-01-27T02:37:31.653Z","path":"gallery/seattle-neighborhoods.html","title":"","comments":1,"layout":"page","_id":"ckz0jkmcu0004nvhw4ad89v32","content":"<div id=\"mapid\"></div>\n<link rel=\"stylesheet\" href=\"style/map.css\" type=\"text/css\">\n<script src=\"src/seattle-json.js\"></script>\n\n","site":{"data":{"projects":[{"name":"koop-provider-pg","url":"https://www.npmjs.com/package/koop-provider-pg","desc":"Koop provider to query PostGIS spatial data"},{"name":"Map Preview","url":"https://packagecontrol.io/packages/MapPreview","desc":"Preview map layer files in your web browser using Sublime Text 3"},{"name":"kotlin mulitplatform template","url":"https://github.com/doneill/kotlin-multiplatform-template","desc":"Kotlin MPP template to bootstrap Mobile/Web"}]}},"excerpt":"","more":"<div id=\"mapid\"></div>\n<link rel=\"stylesheet\" href=\"style/map.css\" type=\"text/css\">\n<script src=\"src/seattle-json.js\"></script>\n\n"},{"title":"uses","date":"2021-12-04T01:42:43.000Z","_content":"\n## Uses\n\n### Code\n- [IntelliJ IDEA](https://www.jetbrains.com/idea/)\n Java/Kotlin development, [Android Studio](https://developer.android.com/studio)\n- [AppCode](https://www.jetbrains.com/objc/)\n Preferred for macOS/iOS development, occasional [Xcode](https://developer.apple.com/xcode/)\n- [Sublime Text](https://www.sublimetext.com/)\n Use it for everything else, Go, Rust, JS, etc., waiting to be accepted into [Fleet](https://www.jetbrains.com/fleet/)\n- [Amp](https://amp.rs/)\n Console editor, vim replacement\n\n### Develpment\n- [Sublime Merge](https://www.sublimemerge.com/)\n When not using Git CLI, more productive reviews\n- [JetBrains Mono](https://www.jetbrains.com/lp/mono/)\n Terminal/Coding font with ligatures, [Fira Code](https://github.com/tonsky/FiraCode) as backup.\n- [iTerm2](https://iterm2.com/)\n MacOS terminal emulator with [oh my zsh](https://ohmyz.sh/). Evaluating [warp](https://www.warp.dev/) \n- [Postico](https://eggerapps.at/postico/)\n Mac PostgreSQL client\n- [Db Browser](https://sqlitebrowser.org/)\n SQLite database browser\n- [Paw](https://paw.cloud/)\n Mac HTTP client\n- [Star UML](https://staruml.io/)\n Model driven development tool\n- [Mockoon](https://mockoon.com/)\n Mock REST APIs\n\n### GIS\n- [QGIS](https://qgis.org)\n Desktop GIS\n- [PostGIS](https://postgis.net/)\n Spatial database extender for PostgreSQL\n- [MapTiler](https://www.maptiler.com/desktop/)\n Spatial data converter into tiles\n- [DBeaver](https://dbeaver.io/)\n Universal database tool supporting spatial data visualization\n- [GDAL](https://gdal.org/)\n Spatial translator for raster/vector data\n- [Tileserv](https://github.com/CrunchyData/pg_tileserv)\n PostGIS tile server\n- [Koop](https://koopjs.github.io/)\n Spatial data transformer, [PostGIS provider](https://github.com/doneill/koop-provider-pg) to transform PostGIS data into ESRI Geoservices\n\n### Productivity\n- [Notion](https://www.notion.so/)\n Logging app\n- [Zettlr](https://www.zettlr.com/)\n Markdown editor\n- [Raycast](https://www.raycast.com/)\n Mac extendible launcher\n- [Browserosaurus](https://github.com/will-stone/browserosaurus)\n Mac browser prompter\n\n### CLI\n- [Silver Searcher](https://github.com/ggreer/the_silver_searcher)\n Code searching tool\n- [bat](https://github.com/sharkdp/bat)\n Cat clone with syntax highlighting and Git integration\n- [exa](https://the.exa.website/)\n Modern replacement for `ls`\n- [fzf](https://github.com/junegunn/fzf)\n Terminal fuzzy finder\n- [Dust](https://github.com/bootandy/dust)\n More intuitive version of `du`\n\n\n","source":"uses/index.md","raw":"---\ntitle: uses\ndate: 2021-12-03 17:42:43\n---\n\n## Uses\n\n### Code\n- [IntelliJ IDEA](https://www.jetbrains.com/idea/)\n Java/Kotlin development, [Android Studio](https://developer.android.com/studio)\n- [AppCode](https://www.jetbrains.com/objc/)\n Preferred for macOS/iOS development, occasional [Xcode](https://developer.apple.com/xcode/)\n- [Sublime Text](https://www.sublimetext.com/)\n Use it for everything else, Go, Rust, JS, etc., waiting to be accepted into [Fleet](https://www.jetbrains.com/fleet/)\n- [Amp](https://amp.rs/)\n Console editor, vim replacement\n\n### Develpment\n- [Sublime Merge](https://www.sublimemerge.com/)\n When not using Git CLI, more productive reviews\n- [JetBrains Mono](https://www.jetbrains.com/lp/mono/)\n Terminal/Coding font with ligatures, [Fira Code](https://github.com/tonsky/FiraCode) as backup.\n- [iTerm2](https://iterm2.com/)\n MacOS terminal emulator with [oh my zsh](https://ohmyz.sh/). Evaluating [warp](https://www.warp.dev/) \n- [Postico](https://eggerapps.at/postico/)\n Mac PostgreSQL client\n- [Db Browser](https://sqlitebrowser.org/)\n SQLite database browser\n- [Paw](https://paw.cloud/)\n Mac HTTP client\n- [Star UML](https://staruml.io/)\n Model driven development tool\n- [Mockoon](https://mockoon.com/)\n Mock REST APIs\n\n### GIS\n- [QGIS](https://qgis.org)\n Desktop GIS\n- [PostGIS](https://postgis.net/)\n Spatial database extender for PostgreSQL\n- [MapTiler](https://www.maptiler.com/desktop/)\n Spatial data converter into tiles\n- [DBeaver](https://dbeaver.io/)\n Universal database tool supporting spatial data visualization\n- [GDAL](https://gdal.org/)\n Spatial translator for raster/vector data\n- [Tileserv](https://github.com/CrunchyData/pg_tileserv)\n PostGIS tile server\n- [Koop](https://koopjs.github.io/)\n Spatial data transformer, [PostGIS provider](https://github.com/doneill/koop-provider-pg) to transform PostGIS data into ESRI Geoservices\n\n### Productivity\n- [Notion](https://www.notion.so/)\n Logging app\n- [Zettlr](https://www.zettlr.com/)\n Markdown editor\n- [Raycast](https://www.raycast.com/)\n Mac extendible launcher\n- [Browserosaurus](https://github.com/will-stone/browserosaurus)\n Mac browser prompter\n\n### CLI\n- [Silver Searcher](https://github.com/ggreer/the_silver_searcher)\n Code searching tool\n- [bat](https://github.com/sharkdp/bat)\n Cat clone with syntax highlighting and Git integration\n- [exa](https://the.exa.website/)\n Modern replacement for `ls`\n- [fzf](https://github.com/junegunn/fzf)\n Terminal fuzzy finder\n- [Dust](https://github.com/bootandy/dust)\n More intuitive version of `du`\n\n\n","updated":"2021-12-04T01:42:43.000Z","path":"uses/index.html","comments":1,"layout":"page","_id":"ckz0jkmcu0005nvhw1get4cts","content":"<h2 id=\"Uses\"><a href=\"#Uses\" class=\"headerlink\" title=\"Uses\"></a>Uses</h2><h3 id=\"Code\"><a href=\"#Code\" class=\"headerlink\" title=\"Code\"></a>Code</h3><ul>\n<li><a href=\"https://www.jetbrains.com/idea/\">IntelliJ IDEA</a><br> Java/Kotlin development, <a href=\"https://developer.android.com/studio\">Android Studio</a></li>\n<li><a href=\"https://www.jetbrains.com/objc/\">AppCode</a><br> Preferred for macOS/iOS development, occasional <a href=\"https://developer.apple.com/xcode/\">Xcode</a></li>\n<li><a href=\"https://www.sublimetext.com/\">Sublime Text</a><br> Use it for everything else, Go, Rust, JS, etc., waiting to be accepted into <a href=\"https://www.jetbrains.com/fleet/\">Fleet</a></li>\n<li><a href=\"https://amp.rs/\">Amp</a><br> Console editor, vim replacement</li>\n</ul>\n<h3 id=\"Develpment\"><a href=\"#Develpment\" class=\"headerlink\" title=\"Develpment\"></a>Develpment</h3><ul>\n<li><a href=\"https://www.sublimemerge.com/\">Sublime Merge</a><br> When not using Git CLI, more productive reviews</li>\n<li><a href=\"https://www.jetbrains.com/lp/mono/\">JetBrains Mono</a><br> Terminal/Coding font with ligatures, <a href=\"https://github.com/tonsky/FiraCode\">Fira Code</a> as backup.</li>\n<li><a href=\"https://iterm2.com/\">iTerm2</a><br> MacOS terminal emulator with <a href=\"https://ohmyz.sh/\">oh my zsh</a>. Evaluating <a href=\"https://www.warp.dev/\">warp</a> </li>\n<li><a href=\"https://eggerapps.at/postico/\">Postico</a><br> Mac PostgreSQL client</li>\n<li><a href=\"https://sqlitebrowser.org/\">Db Browser</a><br> SQLite database browser</li>\n<li><a href=\"https://paw.cloud/\">Paw</a><br> Mac HTTP client</li>\n<li><a href=\"https://staruml.io/\">Star UML</a><br> Model driven development tool</li>\n<li><a href=\"https://mockoon.com/\">Mockoon</a><br> Mock REST APIs</li>\n</ul>\n<h3 id=\"GIS\"><a href=\"#GIS\" class=\"headerlink\" title=\"GIS\"></a>GIS</h3><ul>\n<li><a href=\"https://qgis.org/\">QGIS</a><br> Desktop GIS</li>\n<li><a href=\"https://postgis.net/\">PostGIS</a><br> Spatial database extender for PostgreSQL</li>\n<li><a href=\"https://www.maptiler.com/desktop/\">MapTiler</a><br> Spatial data converter into tiles</li>\n<li><a href=\"https://dbeaver.io/\">DBeaver</a><br> Universal database tool supporting spatial data visualization</li>\n<li><a href=\"https://gdal.org/\">GDAL</a><br> Spatial translator for raster/vector data</li>\n<li><a href=\"https://github.com/CrunchyData/pg_tileserv\">Tileserv</a><br> PostGIS tile server</li>\n<li><a href=\"https://koopjs.github.io/\">Koop</a><br> Spatial data transformer, <a href=\"https://github.com/doneill/koop-provider-pg\">PostGIS provider</a> to transform PostGIS data into ESRI Geoservices</li>\n</ul>\n<h3 id=\"Productivity\"><a href=\"#Productivity\" class=\"headerlink\" title=\"Productivity\"></a>Productivity</h3><ul>\n<li><a href=\"https://www.notion.so/\">Notion</a><br> Logging app</li>\n<li><a href=\"https://www.zettlr.com/\">Zettlr</a><br> Markdown editor</li>\n<li><a href=\"https://www.raycast.com/\">Raycast</a><br> Mac extendible launcher</li>\n<li><a href=\"https://github.com/will-stone/browserosaurus\">Browserosaurus</a><br> Mac browser prompter</li>\n</ul>\n<h3 id=\"CLI\"><a href=\"#CLI\" class=\"headerlink\" title=\"CLI\"></a>CLI</h3><ul>\n<li><a href=\"https://github.com/ggreer/the_silver_searcher\">Silver Searcher</a><br> Code searching tool</li>\n<li><a href=\"https://github.com/sharkdp/bat\">bat</a><br> Cat clone with syntax highlighting and Git integration</li>\n<li><a href=\"https://the.exa.website/\">exa</a><br> Modern replacement for <code>ls</code></li>\n<li><a href=\"https://github.com/junegunn/fzf\">fzf</a><br> Terminal fuzzy finder</li>\n<li><a href=\"https://github.com/bootandy/dust\">Dust</a><br> More intuitive version of <code>du</code></li>\n</ul>\n","site":{"data":{"projects":[{"name":"koop-provider-pg","url":"https://www.npmjs.com/package/koop-provider-pg","desc":"Koop provider to query PostGIS spatial data"},{"name":"Map Preview","url":"https://packagecontrol.io/packages/MapPreview","desc":"Preview map layer files in your web browser using Sublime Text 3"},{"name":"kotlin mulitplatform template","url":"https://github.com/doneill/kotlin-multiplatform-template","desc":"Kotlin MPP template to bootstrap Mobile/Web"}]}},"excerpt":"","more":"<h2 id=\"Uses\"><a href=\"#Uses\" class=\"headerlink\" title=\"Uses\"></a>Uses</h2><h3 id=\"Code\"><a href=\"#Code\" class=\"headerlink\" title=\"Code\"></a>Code</h3><ul>\n<li><a href=\"https://www.jetbrains.com/idea/\">IntelliJ IDEA</a><br> Java/Kotlin development, <a href=\"https://developer.android.com/studio\">Android Studio</a></li>\n<li><a href=\"https://www.jetbrains.com/objc/\">AppCode</a><br> Preferred for macOS/iOS development, occasional <a href=\"https://developer.apple.com/xcode/\">Xcode</a></li>\n<li><a href=\"https://www.sublimetext.com/\">Sublime Text</a><br> Use it for everything else, Go, Rust, JS, etc., waiting to be accepted into <a href=\"https://www.jetbrains.com/fleet/\">Fleet</a></li>\n<li><a href=\"https://amp.rs/\">Amp</a><br> Console editor, vim replacement</li>\n</ul>\n<h3 id=\"Develpment\"><a href=\"#Develpment\" class=\"headerlink\" title=\"Develpment\"></a>Develpment</h3><ul>\n<li><a href=\"https://www.sublimemerge.com/\">Sublime Merge</a><br> When not using Git CLI, more productive reviews</li>\n<li><a href=\"https://www.jetbrains.com/lp/mono/\">JetBrains Mono</a><br> Terminal/Coding font with ligatures, <a href=\"https://github.com/tonsky/FiraCode\">Fira Code</a> as backup.</li>\n<li><a href=\"https://iterm2.com/\">iTerm2</a><br> MacOS terminal emulator with <a href=\"https://ohmyz.sh/\">oh my zsh</a>. Evaluating <a href=\"https://www.warp.dev/\">warp</a> </li>\n<li><a href=\"https://eggerapps.at/postico/\">Postico</a><br> Mac PostgreSQL client</li>\n<li><a href=\"https://sqlitebrowser.org/\">Db Browser</a><br> SQLite database browser</li>\n<li><a href=\"https://paw.cloud/\">Paw</a><br> Mac HTTP client</li>\n<li><a href=\"https://staruml.io/\">Star UML</a><br> Model driven development tool</li>\n<li><a href=\"https://mockoon.com/\">Mockoon</a><br> Mock REST APIs</li>\n</ul>\n<h3 id=\"GIS\"><a href=\"#GIS\" class=\"headerlink\" title=\"GIS\"></a>GIS</h3><ul>\n<li><a href=\"https://qgis.org/\">QGIS</a><br> Desktop GIS</li>\n<li><a href=\"https://postgis.net/\">PostGIS</a><br> Spatial database extender for PostgreSQL</li>\n<li><a href=\"https://www.maptiler.com/desktop/\">MapTiler</a><br> Spatial data converter into tiles</li>\n<li><a href=\"https://dbeaver.io/\">DBeaver</a><br> Universal database tool supporting spatial data visualization</li>\n<li><a href=\"https://gdal.org/\">GDAL</a><br> Spatial translator for raster/vector data</li>\n<li><a href=\"https://github.com/CrunchyData/pg_tileserv\">Tileserv</a><br> PostGIS tile server</li>\n<li><a href=\"https://koopjs.github.io/\">Koop</a><br> Spatial data transformer, <a href=\"https://github.com/doneill/koop-provider-pg\">PostGIS provider</a> to transform PostGIS data into ESRI Geoservices</li>\n</ul>\n<h3 id=\"Productivity\"><a href=\"#Productivity\" class=\"headerlink\" title=\"Productivity\"></a>Productivity</h3><ul>\n<li><a href=\"https://www.notion.so/\">Notion</a><br> Logging app</li>\n<li><a href=\"https://www.zettlr.com/\">Zettlr</a><br> Markdown editor</li>\n<li><a href=\"https://www.raycast.com/\">Raycast</a><br> Mac extendible launcher</li>\n<li><a href=\"https://github.com/will-stone/browserosaurus\">Browserosaurus</a><br> Mac browser prompter</li>\n</ul>\n<h3 id=\"CLI\"><a href=\"#CLI\" class=\"headerlink\" title=\"CLI\"></a>CLI</h3><ul>\n<li><a href=\"https://github.com/ggreer/the_silver_searcher\">Silver Searcher</a><br> Code searching tool</li>\n<li><a href=\"https://github.com/sharkdp/bat\">bat</a><br> Cat clone with syntax highlighting and Git integration</li>\n<li><a href=\"https://the.exa.website/\">exa</a><br> Modern replacement for <code>ls</code></li>\n<li><a href=\"https://github.com/junegunn/fzf\">fzf</a><br> Terminal fuzzy finder</li>\n<li><a href=\"https://github.com/bootandy/dust\">Dust</a><br> More intuitive version of <code>du</code></li>\n</ul>\n"},{"_content":"#mapid {\n width: 100%;\n height: 400px;\n}","source":"gallery/style/map.css","raw":"#mapid {\n width: 100%;\n height: 400px;\n}","date":"2022-01-27T02:37:31.654Z","updated":"2022-01-27T02:37:31.654Z","path":"gallery/style/map.css","layout":"false","title":"","comments":1,"_id":"ckz0jkmcu0006nvhwhk4mfdno","content":"#mapid {\n width: 100%;\n height: 400px;\n}","site":{"data":{"projects":[{"name":"koop-provider-pg","url":"https://www.npmjs.com/package/koop-provider-pg","desc":"Koop provider to query PostGIS spatial data"},{"name":"Map Preview","url":"https://packagecontrol.io/packages/MapPreview","desc":"Preview map layer files in your web browser using Sublime Text 3"},{"name":"kotlin mulitplatform template","url":"https://github.com/doneill/kotlin-multiplatform-template","desc":"Kotlin MPP template to bootstrap Mobile/Web"}]}},"excerpt":"","more":"#mapid {\n width: 100%;\n height: 400px;\n}"},{"_content":"mapLink = '<a href=\"http://openstreetmap.org\">OpenStreetMap</a>'\ngeojsonLink = '<a href=\"https://github.com/CSSEGISandData/COVID-19\">JHU CSSE</a>'\n\nconst tiles = L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png', {\n attribution: '© ' + mapLink + ' Contributors',\n minZoom: 6,\n maxZoom: 8\n})\n\nfunction getColor (d) {\n return d > 100000 ? '#800026'\n : d > 50000 ? '#BD0026'\n : d > 25000 ? '#E31A1C'\n : d > 10000 ? '#FC4E2A'\n : d > 5000 ? '#FD8D3C'\n : d > 1000 ? '#FEB24C'\n : d > 500 ? '#FED976'\n : '#33000000'\n}\n\nfunction stateStyle (feature) {\n return {\n fillColor: getColor(feature.properties.COVID_CONFIRMED),\n weight: 1,\n fillOpacity: 0.7,\n opacity: 0.80\n }\n}\n\n$.getJSON('./data/counties-wa.geojson', function (data) {\n const geojson = L.geoJson(data, {\n style: stateStyle,\n onEachFeature: function (feature, layer) {\n layer.on({\n // mouseover: highlightFeature,\n click: highlightFeature\n })\n }\n })\n\n geojson.getAttribution = function () { return geojsonLink }\n\n const map = L.map('mapid').fitBounds(geojson.getBounds())\n tiles.addTo(map)\n geojson.addTo(map)\n\n function zoomToFeature (e) {\n map.fitBounds(e.target.getBounds())\n }\n\n function highlightFeature (e) {\n const layer = e.target\n info.update(layer.feature.properties)\n }\n\n // control that shows state info on hover\n var info = L.control()\n\n info.onAdd = function (map) {\n this._div = L.DomUtil.create('div', 'info')\n this.update()\n return this._div\n }\n\n info.update = function (props) {\n this._div.innerHTML = (props\n ? '<center><h4>' + props.NAME + '</h4></center><b>' + props.UPDATED + '</b><br><h5>Total Cases: ' + props.COVID_CONFIRMED + '</h5><br><h5>Total Deaths: ' + props.COVID_DEATHS + '</h5>'\n : 'Click/Tap on a county')\n }\n\n info.addTo(map)\n\n const legend = L.control({ position: 'bottomright' })\n\n legend.onAdd = function (map) {\n const div = L.DomUtil.create('div', 'info legend')\n const grades = [0, 500, 1000, 5000, 10000, 25000, 50000, 100000]\n const labels = []\n let from; let to\n\n for (let i = 0; i < grades.length; i++) {\n from = grades[i]\n to = grades[i + 1]\n\n labels.push(\n '<i style=\"background:' + getColor(from + 1) + '\"></i> ' +\n from + (to ? '–' + to : '+'))\n }\n\n div.innerHTML = labels.join('<br>')\n return div\n }\n\n legend.addTo(map)\n})\n","source":"gallery/src/covid-wa-county.js","raw":"mapLink = '<a href=\"http://openstreetmap.org\">OpenStreetMap</a>'\ngeojsonLink = '<a href=\"https://github.com/CSSEGISandData/COVID-19\">JHU CSSE</a>'\n\nconst tiles = L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png', {\n attribution: '© ' + mapLink + ' Contributors',\n minZoom: 6,\n maxZoom: 8\n})\n\nfunction getColor (d) {\n return d > 100000 ? '#800026'\n : d > 50000 ? '#BD0026'\n : d > 25000 ? '#E31A1C'\n : d > 10000 ? '#FC4E2A'\n : d > 5000 ? '#FD8D3C'\n : d > 1000 ? '#FEB24C'\n : d > 500 ? '#FED976'\n : '#33000000'\n}\n\nfunction stateStyle (feature) {\n return {\n fillColor: getColor(feature.properties.COVID_CONFIRMED),\n weight: 1,\n fillOpacity: 0.7,\n opacity: 0.80\n }\n}\n\n$.getJSON('./data/counties-wa.geojson', function (data) {\n const geojson = L.geoJson(data, {\n style: stateStyle,\n onEachFeature: function (feature, layer) {\n layer.on({\n // mouseover: highlightFeature,\n click: highlightFeature\n })\n }\n })\n\n geojson.getAttribution = function () { return geojsonLink }\n\n const map = L.map('mapid').fitBounds(geojson.getBounds())\n tiles.addTo(map)\n geojson.addTo(map)\n\n function zoomToFeature (e) {\n map.fitBounds(e.target.getBounds())\n }\n\n function highlightFeature (e) {\n const layer = e.target\n info.update(layer.feature.properties)\n }\n\n // control that shows state info on hover\n var info = L.control()\n\n info.onAdd = function (map) {\n this._div = L.DomUtil.create('div', 'info')\n this.update()\n return this._div\n }\n\n info.update = function (props) {\n this._div.innerHTML = (props\n ? '<center><h4>' + props.NAME + '</h4></center><b>' + props.UPDATED + '</b><br><h5>Total Cases: ' + props.COVID_CONFIRMED + '</h5><br><h5>Total Deaths: ' + props.COVID_DEATHS + '</h5>'\n : 'Click/Tap on a county')\n }\n\n info.addTo(map)\n\n const legend = L.control({ position: 'bottomright' })\n\n legend.onAdd = function (map) {\n const div = L.DomUtil.create('div', 'info legend')\n const grades = [0, 500, 1000, 5000, 10000, 25000, 50000, 100000]\n const labels = []\n let from; let to\n\n for (let i = 0; i < grades.length; i++) {\n from = grades[i]\n to = grades[i + 1]\n\n labels.push(\n '<i style=\"background:' + getColor(from + 1) + '\"></i> ' +\n from + (to ? '–' + to : '+'))\n }\n\n div.innerHTML = labels.join('<br>')\n return div\n }\n\n legend.addTo(map)\n})\n","date":"2022-01-27T02:37:31.653Z","updated":"2022-01-27T02:37:31.653Z","path":"gallery/src/covid-wa-county.js","layout":"false","title":"","comments":1,"_id":"ckz0jkmcw0007nvhwdl302cxc","content":"mapLink = '<a href=\"http://openstreetmap.org\">OpenStreetMap</a>'\ngeojsonLink = '<a href=\"https://github.com/CSSEGISandData/COVID-19\">JHU CSSE</a>'\n\nconst tiles = L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png', {\n attribution: '© ' + mapLink + ' Contributors',\n minZoom: 6,\n maxZoom: 8\n})\n\nfunction getColor (d) {\n return d > 100000 ? '#800026'\n : d > 50000 ? '#BD0026'\n : d > 25000 ? '#E31A1C'\n : d > 10000 ? '#FC4E2A'\n : d > 5000 ? '#FD8D3C'\n : d > 1000 ? '#FEB24C'\n : d > 500 ? '#FED976'\n : '#33000000'\n}\n\nfunction stateStyle (feature) {\n return {\n fillColor: getColor(feature.properties.COVID_CONFIRMED),\n weight: 1,\n fillOpacity: 0.7,\n opacity: 0.80\n }\n}\n\n$.getJSON('./data/counties-wa.geojson', function (data) {\n const geojson = L.geoJson(data, {\n style: stateStyle,\n onEachFeature: function (feature, layer) {\n layer.on({\n // mouseover: highlightFeature,\n click: highlightFeature\n })\n }\n })\n\n geojson.getAttribution = function () { return geojsonLink }\n\n const map = L.map('mapid').fitBounds(geojson.getBounds())\n tiles.addTo(map)\n geojson.addTo(map)\n\n function zoomToFeature (e) {\n map.fitBounds(e.target.getBounds())\n }\n\n function highlightFeature (e) {\n const layer = e.target\n info.update(layer.feature.properties)\n }\n\n // control that shows state info on hover\n var info = L.control()\n\n info.onAdd = function (map) {\n this._div = L.DomUtil.create('div', 'info')\n this.update()\n return this._div\n }\n\n info.update = function (props) {\n this._div.innerHTML = (props\n ? '<center><h4>' + props.NAME + '</h4></center><b>' + props.UPDATED + '</b><br><h5>Total Cases: ' + props.COVID_CONFIRMED + '</h5><br><h5>Total Deaths: ' + props.COVID_DEATHS + '</h5>'\n : 'Click/Tap on a county')\n }\n\n info.addTo(map)\n\n const legend = L.control({ position: 'bottomright' })\n\n legend.onAdd = function (map) {\n const div = L.DomUtil.create('div', 'info legend')\n const grades = [0, 500, 1000, 5000, 10000, 25000, 50000, 100000]\n const labels = []\n let from; let to\n\n for (let i = 0; i < grades.length; i++) {\n from = grades[i]\n to = grades[i + 1]\n\n labels.push(\n '<i style=\"background:' + getColor(from + 1) + '\"></i> ' +\n from + (to ? '–' + to : '+'))\n }\n\n div.innerHTML = labels.join('<br>')\n return div\n }\n\n legend.addTo(map)\n})\n","site":{"data":{"projects":[{"name":"koop-provider-pg","url":"https://www.npmjs.com/package/koop-provider-pg","desc":"Koop provider to query PostGIS spatial data"},{"name":"Map Preview","url":"https://packagecontrol.io/packages/MapPreview","desc":"Preview map layer files in your web browser using Sublime Text 3"},{"name":"kotlin mulitplatform template","url":"https://github.com/doneill/kotlin-multiplatform-template","desc":"Kotlin MPP template to bootstrap Mobile/Web"}]}},"excerpt":"","more":"mapLink = '<a href=\"http://openstreetmap.org\">OpenStreetMap</a>'\ngeojsonLink = '<a href=\"https://github.com/CSSEGISandData/COVID-19\">JHU CSSE</a>'\n\nconst tiles = L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png', {\n attribution: '© ' + mapLink + ' Contributors',\n minZoom: 6,\n maxZoom: 8\n})\n\nfunction getColor (d) {\n return d > 100000 ? '#800026'\n : d > 50000 ? '#BD0026'\n : d > 25000 ? '#E31A1C'\n : d > 10000 ? '#FC4E2A'\n : d > 5000 ? '#FD8D3C'\n : d > 1000 ? '#FEB24C'\n : d > 500 ? '#FED976'\n : '#33000000'\n}\n\nfunction stateStyle (feature) {\n return {\n fillColor: getColor(feature.properties.COVID_CONFIRMED),\n weight: 1,\n fillOpacity: 0.7,\n opacity: 0.80\n }\n}\n\n$.getJSON('./data/counties-wa.geojson', function (data) {\n const geojson = L.geoJson(data, {\n style: stateStyle,\n onEachFeature: function (feature, layer) {\n layer.on({\n // mouseover: highlightFeature,\n click: highlightFeature\n })\n }\n })\n\n geojson.getAttribution = function () { return geojsonLink }\n\n const map = L.map('mapid').fitBounds(geojson.getBounds())\n tiles.addTo(map)\n geojson.addTo(map)\n\n function zoomToFeature (e) {\n map.fitBounds(e.target.getBounds())\n }\n\n function highlightFeature (e) {\n const layer = e.target\n info.update(layer.feature.properties)\n }\n\n // control that shows state info on hover\n var info = L.control()\n\n info.onAdd = function (map) {\n this._div = L.DomUtil.create('div', 'info')\n this.update()\n return this._div\n }\n\n info.update = function (props) {\n this._div.innerHTML = (props\n ? '<center><h4>' + props.NAME + '</h4></center><b>' + props.UPDATED + '</b><br><h5>Total Cases: ' + props.COVID_CONFIRMED + '</h5><br><h5>Total Deaths: ' + props.COVID_DEATHS + '</h5>'\n : 'Click/Tap on a county')\n }\n\n info.addTo(map)\n\n const legend = L.control({ position: 'bottomright' })\n\n legend.onAdd = function (map) {\n const div = L.DomUtil.create('div', 'info legend')\n const grades = [0, 500, 1000, 5000, 10000, 25000, 50000, 100000]\n const labels = []\n let from; let to\n\n for (let i = 0; i < grades.length; i++) {\n from = grades[i]\n to = grades[i + 1]\n\n labels.push(\n '<i style=\"background:' + getColor(from + 1) + '\"></i> ' +\n from + (to ? '–' + to : '+'))\n }\n\n div.innerHTML = labels.join('<br>')\n return div\n }\n\n legend.addTo(map)\n})\n"},{"_content":"mapLink = '<a href=\"http://openstreetmap.org\">OpenStreetMap</a>'\ngeojsonLink = '<a href=\"https://www.worldometers.info/coronavirus/country/us/\">worldometer</a>'\n\nconst tiles = L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png', {\n attribution: '© ' + mapLink + ' Contributors',\n minZoom: 3,\n maxZoom: 6\n})\n\nfunction getColor (d) {\n return d > 3000000 ? '#800026'\n : d > 2000000 ? '#BD0026'\n : d > 1000000 ? '#E31A1C'\n : d > 750000 ? '#FC4E2A'\n : d > 500000 ? '#FD8D3C'\n : d > 150000 ? '#FEB24C'\n : d > 75000 ? '#FED976'\n : '#FFEDA0'\n}\n\nfunction stateStyle (feature) {\n return {\n weight: 1,\n fillOpacity: 0.7,\n opacity: 0.45,\n fillColor: getColor(feature.properties.COVID_CONFIRMED)\n }\n}\n\n$.getJSON('./data/states.geojson', function (data) {\n const geojson = L.geoJson(data, {\n style: stateStyle,\n onEachFeature: function (feature, layer) {\n layer.on({\n // mouseover: highlightFeature,\n click: highlightFeature\n })\n }\n })\n\n geojson.getAttribution = function () { return geojsonLink }\n\n const map = L.map('mapid').setView([37.8, -96], 4)\n tiles.addTo(map)\n geojson.addTo(map)\n\n function zoomToFeature (e) {\n map.fitBounds(e.target.getBounds())\n }\n\n function highlightFeature (e) {\n const layer = e.target\n info.update(layer.feature.properties)\n }\n\n // control that shows state info on hover\n var info = L.control()\n\n info.onAdd = function (map) {\n this._div = L.DomUtil.create('div', 'info')\n this.update()\n return this._div\n }\n\n info.update = function (props) {\n this._div.innerHTML = (props\n ? '<center><h4>' + props.NAME + '</h4></center><br><b>Total Cases: </b>' + props.COVID_CONFIRMED + '<br><b>New Cases: </b>' + props.COVID_NEW + '<br><b>Total Deaths: </b>' + props.COVID_DEATHS\n : 'Click/Tap on a state')\n }\n\n info.addTo(map)\n\n const legend = L.control({ position: 'bottomright' })\n\n legend.onAdd = function (map) {\n const div = L.DomUtil.create('div', 'info legend')\n const grades = [0, 75000, 150000, 500000, 750000, 1000000, 2000000, 3000000]\n const labels = []\n let from; let to\n\n for (let i = 0; i < grades.length; i++) {\n from = grades[i]\n to = grades[i + 1]\n\n labels.push(\n '<i style=\"background:' + getColor(from + 1) + '\"></i> ' +\n from + (to ? '–' + to : '+'))\n }\n\n div.innerHTML = labels.join('<br>')\n return div\n }\n\n legend.addTo(map)\n})\n","source":"gallery/src/covid.js","raw":"mapLink = '<a href=\"http://openstreetmap.org\">OpenStreetMap</a>'\ngeojsonLink = '<a href=\"https://www.worldometers.info/coronavirus/country/us/\">worldometer</a>'\n\nconst tiles = L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png', {\n attribution: '© ' + mapLink + ' Contributors',\n minZoom: 3,\n maxZoom: 6\n})\n\nfunction getColor (d) {\n return d > 3000000 ? '#800026'\n : d > 2000000 ? '#BD0026'\n : d > 1000000 ? '#E31A1C'\n : d > 750000 ? '#FC4E2A'\n : d > 500000 ? '#FD8D3C'\n : d > 150000 ? '#FEB24C'\n : d > 75000 ? '#FED976'\n : '#FFEDA0'\n}\n\nfunction stateStyle (feature) {\n return {\n weight: 1,\n fillOpacity: 0.7,\n opacity: 0.45,\n fillColor: getColor(feature.properties.COVID_CONFIRMED)\n }\n}\n\n$.getJSON('./data/states.geojson', function (data) {\n const geojson = L.geoJson(data, {\n style: stateStyle,\n onEachFeature: function (feature, layer) {\n layer.on({\n // mouseover: highlightFeature,\n click: highlightFeature\n })\n }\n })\n\n geojson.getAttribution = function () { return geojsonLink }\n\n const map = L.map('mapid').setView([37.8, -96], 4)\n tiles.addTo(map)\n geojson.addTo(map)\n\n function zoomToFeature (e) {\n map.fitBounds(e.target.getBounds())\n }\n\n function highlightFeature (e) {\n const layer = e.target\n info.update(layer.feature.properties)\n }\n\n // control that shows state info on hover\n var info = L.control()\n\n info.onAdd = function (map) {\n this._div = L.DomUtil.create('div', 'info')\n this.update()\n return this._div\n }\n\n info.update = function (props) {\n this._div.innerHTML = (props\n ? '<center><h4>' + props.NAME + '</h4></center><br><b>Total Cases: </b>' + props.COVID_CONFIRMED + '<br><b>New Cases: </b>' + props.COVID_NEW + '<br><b>Total Deaths: </b>' + props.COVID_DEATHS\n : 'Click/Tap on a state')\n }\n\n info.addTo(map)\n\n const legend = L.control({ position: 'bottomright' })\n\n legend.onAdd = function (map) {\n const div = L.DomUtil.create('div', 'info legend')\n const grades = [0, 75000, 150000, 500000, 750000, 1000000, 2000000, 3000000]\n const labels = []\n let from; let to\n\n for (let i = 0; i < grades.length; i++) {\n from = grades[i]\n to = grades[i + 1]\n\n labels.push(\n '<i style=\"background:' + getColor(from + 1) + '\"></i> ' +\n from + (to ? '–' + to : '+'))\n }\n\n div.innerHTML = labels.join('<br>')\n return div\n }\n\n legend.addTo(map)\n})\n","date":"2022-01-27T02:37:31.653Z","updated":"2022-01-27T02:37:31.653Z","path":"gallery/src/covid.js","layout":"false","title":"","comments":1,"_id":"ckz0jkmcx0009nvhwcyl17whr","content":"mapLink = '<a href=\"http://openstreetmap.org\">OpenStreetMap</a>'\ngeojsonLink = '<a href=\"https://www.worldometers.info/coronavirus/country/us/\">worldometer</a>'\n\nconst tiles = L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png', {\n attribution: '© ' + mapLink + ' Contributors',\n minZoom: 3,\n maxZoom: 6\n})\n\nfunction getColor (d) {\n return d > 3000000 ? '#800026'\n : d > 2000000 ? '#BD0026'\n : d > 1000000 ? '#E31A1C'\n : d > 750000 ? '#FC4E2A'\n : d > 500000 ? '#FD8D3C'\n : d > 150000 ? '#FEB24C'\n : d > 75000 ? '#FED976'\n : '#FFEDA0'\n}\n\nfunction stateStyle (feature) {\n return {\n weight: 1,\n fillOpacity: 0.7,\n opacity: 0.45,\n fillColor: getColor(feature.properties.COVID_CONFIRMED)\n }\n}\n\n$.getJSON('./data/states.geojson', function (data) {\n const geojson = L.geoJson(data, {\n style: stateStyle,\n onEachFeature: function (feature, layer) {\n layer.on({\n // mouseover: highlightFeature,\n click: highlightFeature\n })\n }\n })\n\n geojson.getAttribution = function () { return geojsonLink }\n\n const map = L.map('mapid').setView([37.8, -96], 4)\n tiles.addTo(map)\n geojson.addTo(map)\n\n function zoomToFeature (e) {\n map.fitBounds(e.target.getBounds())\n }\n\n function highlightFeature (e) {\n const layer = e.target\n info.update(layer.feature.properties)\n }\n\n // control that shows state info on hover\n var info = L.control()\n\n info.onAdd = function (map) {\n this._div = L.DomUtil.create('div', 'info')\n this.update()\n return this._div\n }\n\n info.update = function (props) {\n this._div.innerHTML = (props\n ? '<center><h4>' + props.NAME + '</h4></center><br><b>Total Cases: </b>' + props.COVID_CONFIRMED + '<br><b>New Cases: </b>' + props.COVID_NEW + '<br><b>Total Deaths: </b>' + props.COVID_DEATHS\n : 'Click/Tap on a state')\n }\n\n info.addTo(map)\n\n const legend = L.control({ position: 'bottomright' })\n\n legend.onAdd = function (map) {\n const div = L.DomUtil.create('div', 'info legend')\n const grades = [0, 75000, 150000, 500000, 750000, 1000000, 2000000, 3000000]\n const labels = []\n let from; let to\n\n for (let i = 0; i < grades.length; i++) {\n from = grades[i]\n to = grades[i + 1]\n\n labels.push(\n '<i style=\"background:' + getColor(from + 1) + '\"></i> ' +\n from + (to ? '–' + to : '+'))\n }\n\n div.innerHTML = labels.join('<br>')\n return div\n }\n\n legend.addTo(map)\n})\n","site":{"data":{"projects":[{"name":"koop-provider-pg","url":"https://www.npmjs.com/package/koop-provider-pg","desc":"Koop provider to query PostGIS spatial data"},{"name":"Map Preview","url":"https://packagecontrol.io/packages/MapPreview","desc":"Preview map layer files in your web browser using Sublime Text 3"},{"name":"kotlin mulitplatform template","url":"https://github.com/doneill/kotlin-multiplatform-template","desc":"Kotlin MPP template to bootstrap Mobile/Web"}]}},"excerpt":"","more":"mapLink = '<a href=\"http://openstreetmap.org\">OpenStreetMap</a>'\ngeojsonLink = '<a href=\"https://www.worldometers.info/coronavirus/country/us/\">worldometer</a>'\n\nconst tiles = L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png', {\n attribution: '© ' + mapLink + ' Contributors',\n minZoom: 3,\n maxZoom: 6\n})\n\nfunction getColor (d) {\n return d > 3000000 ? '#800026'\n : d > 2000000 ? '#BD0026'\n : d > 1000000 ? '#E31A1C'\n : d > 750000 ? '#FC4E2A'\n : d > 500000 ? '#FD8D3C'\n : d > 150000 ? '#FEB24C'\n : d > 75000 ? '#FED976'\n : '#FFEDA0'\n}\n\nfunction stateStyle (feature) {\n return {\n weight: 1,\n fillOpacity: 0.7,\n opacity: 0.45,\n fillColor: getColor(feature.properties.COVID_CONFIRMED)\n }\n}\n\n$.getJSON('./data/states.geojson', function (data) {\n const geojson = L.geoJson(data, {\n style: stateStyle,\n onEachFeature: function (feature, layer) {\n layer.on({\n // mouseover: highlightFeature,\n click: highlightFeature\n })\n }\n })\n\n geojson.getAttribution = function () { return geojsonLink }\n\n const map = L.map('mapid').setView([37.8, -96], 4)\n tiles.addTo(map)\n geojson.addTo(map)\n\n function zoomToFeature (e) {\n map.fitBounds(e.target.getBounds())\n }\n\n function highlightFeature (e) {\n const layer = e.target\n info.update(layer.feature.properties)\n }\n\n // control that shows state info on hover\n var info = L.control()\n\n info.onAdd = function (map) {\n this._div = L.DomUtil.create('div', 'info')\n this.update()\n return this._div\n }\n\n info.update = function (props) {\n this._div.innerHTML = (props\n ? '<center><h4>' + props.NAME + '</h4></center><br><b>Total Cases: </b>' + props.COVID_CONFIRMED + '<br><b>New Cases: </b>' + props.COVID_NEW + '<br><b>Total Deaths: </b>' + props.COVID_DEATHS\n : 'Click/Tap on a state')\n }\n\n info.addTo(map)\n\n const legend = L.control({ position: 'bottomright' })\n\n legend.onAdd = function (map) {\n const div = L.DomUtil.create('div', 'info legend')\n const grades = [0, 75000, 150000, 500000, 750000, 1000000, 2000000, 3000000]\n const labels = []\n let from; let to\n\n for (let i = 0; i < grades.length; i++) {\n from = grades[i]\n to = grades[i + 1]\n\n labels.push(\n '<i style=\"background:' + getColor(from + 1) + '\"></i> ' +\n from + (to ? '–' + to : '+'))\n }\n\n div.innerHTML = labels.join('<br>')\n return div\n }\n\n legend.addTo(map)\n})\n"},{"_content":"const fullScreenControl = new ol.control.FullScreen()\nconst scaleLineControl = new ol.control.ScaleLine()\n\nconst map = new ol.Map({\n target: 'map',\n controls: ol.control.defaults().extend([\n fullScreenControl,\n scaleLineControl\n ]),\n layers: [\n new ol.layer.Tile({\n source: new ol.source.OSM()\n }),\n new ol.layer.Tile({\n source: new ol.source.XYZ({\n attributions: [\n 'NOAA © <a href=\"https://nauticalcharts.noaa.gov/data/gis-data-and-services.html\">RNC Tileset</a>',\n ol.control.ATTRIBUTION],\n url: 'http://tileservice.charts.noaa.gov/tiles/11013_1/{z}/{x}/{y}.png'\n })\n })\n ],\n view: new ol.View({\n center: ol.proj.fromLonLat([-80.862921, 24.798830]),\n zoom: 8\n })\n})\n\nconst zoomslider = new ol.control.ZoomSlider()\nmap.addControl(zoomslider)\n","source":"gallery/src/fl-straits.js","raw":"const fullScreenControl = new ol.control.FullScreen()\nconst scaleLineControl = new ol.control.ScaleLine()\n\nconst map = new ol.Map({\n target: 'map',\n controls: ol.control.defaults().extend([\n fullScreenControl,\n scaleLineControl\n ]),\n layers: [\n new ol.layer.Tile({\n source: new ol.source.OSM()\n }),\n new ol.layer.Tile({\n source: new ol.source.XYZ({\n attributions: [\n 'NOAA © <a href=\"https://nauticalcharts.noaa.gov/data/gis-data-and-services.html\">RNC Tileset</a>',\n ol.control.ATTRIBUTION],\n url: 'http://tileservice.charts.noaa.gov/tiles/11013_1/{z}/{x}/{y}.png'\n })\n })\n ],\n view: new ol.View({\n center: ol.proj.fromLonLat([-80.862921, 24.798830]),\n zoom: 8\n })\n})\n\nconst zoomslider = new ol.control.ZoomSlider()\nmap.addControl(zoomslider)\n","date":"2022-01-27T02:37:31.653Z","updated":"2022-01-27T02:37:31.653Z","path":"gallery/src/fl-straits.js","layout":"false","title":"","comments":1,"_id":"ckz0jkmcz000cnvhw0di5e2hr","content":"const fullScreenControl = new ol.control.FullScreen()\nconst scaleLineControl = new ol.control.ScaleLine()\n\nconst map = new ol.Map({\n target: 'map',\n controls: ol.control.defaults().extend([\n fullScreenControl,\n scaleLineControl\n ]),\n layers: [\n new ol.layer.Tile({\n source: new ol.source.OSM()\n }),\n new ol.layer.Tile({\n source: new ol.source.XYZ({\n attributions: [\n 'NOAA © <a href=\"https://nauticalcharts.noaa.gov/data/gis-data-and-services.html\">RNC Tileset</a>',\n ol.control.ATTRIBUTION],\n url: 'http://tileservice.charts.noaa.gov/tiles/11013_1/{z}/{x}/{y}.png'\n })\n })\n ],\n view: new ol.View({\n center: ol.proj.fromLonLat([-80.862921, 24.798830]),\n zoom: 8\n })\n})\n\nconst zoomslider = new ol.control.ZoomSlider()\nmap.addControl(zoomslider)\n","site":{"data":{"projects":[{"name":"koop-provider-pg","url":"https://www.npmjs.com/package/koop-provider-pg","desc":"Koop provider to query PostGIS spatial data"},{"name":"Map Preview","url":"https://packagecontrol.io/packages/MapPreview","desc":"Preview map layer files in your web browser using Sublime Text 3"},{"name":"kotlin mulitplatform template","url":"https://github.com/doneill/kotlin-multiplatform-template","desc":"Kotlin MPP template to bootstrap Mobile/Web"}]}},"excerpt":"","more":"const fullScreenControl = new ol.control.FullScreen()\nconst scaleLineControl = new ol.control.ScaleLine()\n\nconst map = new ol.Map({\n target: 'map',\n controls: ol.control.defaults().extend([\n fullScreenControl,\n scaleLineControl\n ]),\n layers: [\n new ol.layer.Tile({\n source: new ol.source.OSM()\n }),\n new ol.layer.Tile({\n source: new ol.source.XYZ({\n attributions: [\n 'NOAA © <a href=\"https://nauticalcharts.noaa.gov/data/gis-data-and-services.html\">RNC Tileset</a>',\n ol.control.ATTRIBUTION],\n url: 'http://tileservice.charts.noaa.gov/tiles/11013_1/{z}/{x}/{y}.png'\n })\n })\n ],\n view: new ol.View({\n center: ol.proj.fromLonLat([-80.862921, 24.798830]),\n zoom: 8\n })\n})\n\nconst zoomslider = new ol.control.ZoomSlider()\nmap.addControl(zoomslider)\n"},{"_content":"/*! modernizr 3.6.0 (Custom Build) | MIT *\n * https://modernizr.com/download/?-setclasses !*/\n!function(n,e,s){function o(n,e){return typeof n===e}function a(){var n,e,s,a,i,l,r;for(var c in f)if(f.hasOwnProperty(c)){if(n=[],e=f[c],e.name&&(n.push(e.name.toLowerCase()),e.options&&e.options.aliases&&e.options.aliases.length))for(s=0;s<e.options.aliases.length;s++)n.push(e.options.aliases[s].toLowerCase());for(a=o(e.fn,\"function\")?e.fn():e.fn,i=0;i<n.length;i++)l=n[i],r=l.split(\".\"),1===r.length?Modernizr[r[0]]=a:(!Modernizr[r[0]]||Modernizr[r[0]]instanceof Boolean||(Modernizr[r[0]]=new Boolean(Modernizr[r[0]])),Modernizr[r[0]][r[1]]=a),t.push((a?\"\":\"no-\")+r.join(\"-\"))}}function i(n){var e=r.className,s=Modernizr._config.classPrefix||\"\";if(c&&(e=e.baseVal),Modernizr._config.enableJSClass){var o=new RegExp(\"(^|\\\\s)\"+s+\"no-js(\\\\s|$)\");e=e.replace(o,\"$1\"+s+\"js$2\")}Modernizr._config.enableClasses&&(e+=\" \"+s+n.join(\" \"+s),c?r.className.baseVal=e:r.className=e)}var t=[],f=[],l={_version:\"3.6.0\",_config:{classPrefix:\"\",enableClasses:!0,enableJSClass:!0,usePrefixes:!0},_q:[],on:function(n,e){var s=this;setTimeout(function(){e(s[n])},0)},addTest:function(n,e,s){f.push({name:n,fn:e,options:s})},addAsyncTest:function(n){f.push({name:null,fn:n})}},Modernizr=function(){};Modernizr.prototype=l,Modernizr=new Modernizr;var r=e.documentElement,c=\"svg\"===r.nodeName.toLowerCase();a(),i(t),delete l.addTest,delete l.addAsyncTest;for(var u=0;u<Modernizr._q.length;u++)Modernizr._q[u]();n.Modernizr=Modernizr}(window,document);","source":"gallery/src/modernizr-custom.js","raw":"/*! modernizr 3.6.0 (Custom Build) | MIT *\n * https://modernizr.com/download/?-setclasses !*/\n!function(n,e,s){function o(n,e){return typeof n===e}function a(){var n,e,s,a,i,l,r;for(var c in f)if(f.hasOwnProperty(c)){if(n=[],e=f[c],e.name&&(n.push(e.name.toLowerCase()),e.options&&e.options.aliases&&e.options.aliases.length))for(s=0;s<e.options.aliases.length;s++)n.push(e.options.aliases[s].toLowerCase());for(a=o(e.fn,\"function\")?e.fn():e.fn,i=0;i<n.length;i++)l=n[i],r=l.split(\".\"),1===r.length?Modernizr[r[0]]=a:(!Modernizr[r[0]]||Modernizr[r[0]]instanceof Boolean||(Modernizr[r[0]]=new Boolean(Modernizr[r[0]])),Modernizr[r[0]][r[1]]=a),t.push((a?\"\":\"no-\")+r.join(\"-\"))}}function i(n){var e=r.className,s=Modernizr._config.classPrefix||\"\";if(c&&(e=e.baseVal),Modernizr._config.enableJSClass){var o=new RegExp(\"(^|\\\\s)\"+s+\"no-js(\\\\s|$)\");e=e.replace(o,\"$1\"+s+\"js$2\")}Modernizr._config.enableClasses&&(e+=\" \"+s+n.join(\" \"+s),c?r.className.baseVal=e:r.className=e)}var t=[],f=[],l={_version:\"3.6.0\",_config:{classPrefix:\"\",enableClasses:!0,enableJSClass:!0,usePrefixes:!0},_q:[],on:function(n,e){var s=this;setTimeout(function(){e(s[n])},0)},addTest:function(n,e,s){f.push({name:n,fn:e,options:s})},addAsyncTest:function(n){f.push({name:null,fn:n})}},Modernizr=function(){};Modernizr.prototype=l,Modernizr=new Modernizr;var r=e.documentElement,c=\"svg\"===r.nodeName.toLowerCase();a(),i(t),delete l.addTest,delete l.addAsyncTest;for(var u=0;u<Modernizr._q.length;u++)Modernizr._q[u]();n.Modernizr=Modernizr}(window,document);","date":"2022-01-27T02:37:31.653Z","updated":"2022-01-27T02:37:31.653Z","path":"gallery/src/modernizr-custom.js","layout":"false","title":"","comments":1,"_id":"ckz0jkmcz000envhwgmpd3xft","content":"/*! modernizr 3.6.0 (Custom Build) | MIT *\n * https://modernizr.com/download/?-setclasses !*/\n!function(n,e,s){function o(n,e){return typeof n===e}function a(){var n,e,s,a,i,l,r;for(var c in f)if(f.hasOwnProperty(c)){if(n=[],e=f[c],e.name&&(n.push(e.name.toLowerCase()),e.options&&e.options.aliases&&e.options.aliases.length))for(s=0;s<e.options.aliases.length;s++)n.push(e.options.aliases[s].toLowerCase());for(a=o(e.fn,\"function\")?e.fn():e.fn,i=0;i<n.length;i++)l=n[i],r=l.split(\".\"),1===r.length?Modernizr[r[0]]=a:(!Modernizr[r[0]]||Modernizr[r[0]]instanceof Boolean||(Modernizr[r[0]]=new Boolean(Modernizr[r[0]])),Modernizr[r[0]][r[1]]=a),t.push((a?\"\":\"no-\")+r.join(\"-\"))}}function i(n){var e=r.className,s=Modernizr._config.classPrefix||\"\";if(c&&(e=e.baseVal),Modernizr._config.enableJSClass){var o=new RegExp(\"(^|\\\\s)\"+s+\"no-js(\\\\s|$)\");e=e.replace(o,\"$1\"+s+\"js$2\")}Modernizr._config.enableClasses&&(e+=\" \"+s+n.join(\" \"+s),c?r.className.baseVal=e:r.className=e)}var t=[],f=[],l={_version:\"3.6.0\",_config:{classPrefix:\"\",enableClasses:!0,enableJSClass:!0,usePrefixes:!0},_q:[],on:function(n,e){var s=this;setTimeout(function(){e(s[n])},0)},addTest:function(n,e,s){f.push({name:n,fn:e,options:s})},addAsyncTest:function(n){f.push({name:null,fn:n})}},Modernizr=function(){};Modernizr.prototype=l,Modernizr=new Modernizr;var r=e.documentElement,c=\"svg\"===r.nodeName.toLowerCase();a(),i(t),delete l.addTest,delete l.addAsyncTest;for(var u=0;u<Modernizr._q.length;u++)Modernizr._q[u]();n.Modernizr=Modernizr}(window,document);","site":{"data":{"projects":[{"name":"koop-provider-pg","url":"https://www.npmjs.com/package/koop-provider-pg","desc":"Koop provider to query PostGIS spatial data"},{"name":"Map Preview","url":"https://packagecontrol.io/packages/MapPreview","desc":"Preview map layer files in your web browser using Sublime Text 3"},{"name":"kotlin mulitplatform template","url":"https://github.com/doneill/kotlin-multiplatform-template","desc":"Kotlin MPP template to bootstrap Mobile/Web"}]}},"excerpt":"","more":"/*! modernizr 3.6.0 (Custom Build) | MIT *\n * https://modernizr.com/download/?-setclasses !*/\n!function(n,e,s){function o(n,e){return typeof n===e}function a(){var n,e,s,a,i,l,r;for(var c in f)if(f.hasOwnProperty(c)){if(n=[],e=f[c],e.name&&(n.push(e.name.toLowerCase()),e.options&&e.options.aliases&&e.options.aliases.length))for(s=0;s<e.options.aliases.length;s++)n.push(e.options.aliases[s].toLowerCase());for(a=o(e.fn,\"function\")?e.fn():e.fn,i=0;i<n.length;i++)l=n[i],r=l.split(\".\"),1===r.length?Modernizr[r[0]]=a:(!Modernizr[r[0]]||Modernizr[r[0]]instanceof Boolean||(Modernizr[r[0]]=new Boolean(Modernizr[r[0]])),Modernizr[r[0]][r[1]]=a),t.push((a?\"\":\"no-\")+r.join(\"-\"))}}function i(n){var e=r.className,s=Modernizr._config.classPrefix||\"\";if(c&&(e=e.baseVal),Modernizr._config.enableJSClass){var o=new RegExp(\"(^|\\\\s)\"+s+\"no-js(\\\\s|$)\");e=e.replace(o,\"$1\"+s+\"js$2\")}Modernizr._config.enableClasses&&(e+=\" \"+s+n.join(\" \"+s),c?r.className.baseVal=e:r.className=e)}var t=[],f=[],l={_version:\"3.6.0\",_config:{classPrefix:\"\",enableClasses:!0,enableJSClass:!0,usePrefixes:!0},_q:[],on:function(n,e){var s=this;setTimeout(function(){e(s[n])},0)},addTest:function(n,e,s){f.push({name:n,fn:e,options:s})},addAsyncTest:function(n){f.push({name:null,fn:n})}},Modernizr=function(){};Modernizr.prototype=l,Modernizr=new Modernizr;var r=e.documentElement,c=\"svg\"===r.nodeName.toLowerCase();a(),i(t),delete l.addTest,delete l.addAsyncTest;for(var u=0;u<Modernizr._q.length;u++)Modernizr._q[u]();n.Modernizr=Modernizr}(window,document);"},{"_content":"mapLink = '<a href=\"http://openstreetmap.org\">OpenStreetMap</a>'\n\nconst tiles = L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png', {\n attribution: '© ' + mapLink + ' Contributors',\n minZoom: 8,\n maxZoom: 18\n})\n\nfunction getColor (d) {\n return d > 300000000 ? '#4682B4'\n : d > 250000000 ? '#6495ED'\n : d > 100000000 ? '#1E90FF'\n : d > 50000000 ? '#B0C4DE'\n : d > 10000000 ? '#00BFFF'\n : d > 7500000 ? '#87CEEB'\n : d > 5000000 ? '#87CEFA'\n : '#ADD8E6'\n}\n\nfunction setStyle (feature) {\n return {\n fillColor: getColor(feature.properties.area),\n weight: 1,\n fillOpacity: 0.7,\n opacity: 0.80\n }\n}\n\n$.getJSON('./data/seattle-neighborhoods.geojson', function (data) {\n const geojson = L.geoJson(data, {\n style: setStyle,\n onEachFeature: function (feature, layer) {\n layer.bindPopup('<b>Name: </b>' + feature.properties.name + '<br><b>Area: </b>' + feature.properties.area)\n }\n })\n\n const map = L.map('mapid').fitBounds(geojson.getBounds())\n tiles.addTo(map)\n geojson.addTo(map)\n})\n","source":"gallery/src/seattle-json.js","raw":"mapLink = '<a href=\"http://openstreetmap.org\">OpenStreetMap</a>'\n\nconst tiles = L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png', {\n attribution: '© ' + mapLink + ' Contributors',\n minZoom: 8,\n maxZoom: 18\n})\n\nfunction getColor (d) {\n return d > 300000000 ? '#4682B4'\n : d > 250000000 ? '#6495ED'\n : d > 100000000 ? '#1E90FF'\n : d > 50000000 ? '#B0C4DE'\n : d > 10000000 ? '#00BFFF'\n : d > 7500000 ? '#87CEEB'\n : d > 5000000 ? '#87CEFA'\n : '#ADD8E6'\n}\n\nfunction setStyle (feature) {\n return {\n fillColor: getColor(feature.properties.area),\n weight: 1,\n fillOpacity: 0.7,\n opacity: 0.80\n }\n}\n\n$.getJSON('./data/seattle-neighborhoods.geojson', function (data) {\n const geojson = L.geoJson(data, {\n style: setStyle,\n onEachFeature: function (feature, layer) {\n layer.bindPopup('<b>Name: </b>' + feature.properties.name + '<br><b>Area: </b>' + feature.properties.area)\n }\n })\n\n const map = L.map('mapid').fitBounds(geojson.getBounds())\n tiles.addTo(map)\n geojson.addTo(map)\n})\n","date":"2022-01-27T02:37:31.653Z","updated":"2022-01-27T02:37:31.653Z","path":"gallery/src/seattle-json.js","layout":"false","title":"","comments":1,"_id":"ckz0jkmd0000gnvhwexe2hs1r","content":"mapLink = '<a href=\"http://openstreetmap.org\">OpenStreetMap</a>'\n\nconst tiles = L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png', {\n attribution: '© ' + mapLink + ' Contributors',\n minZoom: 8,\n maxZoom: 18\n})\n\nfunction getColor (d) {\n return d > 300000000 ? '#4682B4'\n : d > 250000000 ? '#6495ED'\n : d > 100000000 ? '#1E90FF'\n : d > 50000000 ? '#B0C4DE'\n : d > 10000000 ? '#00BFFF'\n : d > 7500000 ? '#87CEEB'\n : d > 5000000 ? '#87CEFA'\n : '#ADD8E6'\n}\n\nfunction setStyle (feature) {\n return {\n fillColor: getColor(feature.properties.area),\n weight: 1,\n fillOpacity: 0.7,\n opacity: 0.80\n }\n}\n\n$.getJSON('./data/seattle-neighborhoods.geojson', function (data) {\n const geojson = L.geoJson(data, {\n style: setStyle,\n onEachFeature: function (feature, layer) {\n layer.bindPopup('<b>Name: </b>' + feature.properties.name + '<br><b>Area: </b>' + feature.properties.area)\n }\n })\n\n const map = L.map('mapid').fitBounds(geojson.getBounds())\n tiles.addTo(map)\n geojson.addTo(map)\n})\n","site":{"data":{"projects":[{"name":"koop-provider-pg","url":"https://www.npmjs.com/package/koop-provider-pg","desc":"Koop provider to query PostGIS spatial data"},{"name":"Map Preview","url":"https://packagecontrol.io/packages/MapPreview","desc":"Preview map layer files in your web browser using Sublime Text 3"},{"name":"kotlin mulitplatform template","url":"https://github.com/doneill/kotlin-multiplatform-template","desc":"Kotlin MPP template to bootstrap Mobile/Web"}]}},"excerpt":"","more":"mapLink = '<a href=\"http://openstreetmap.org\">OpenStreetMap</a>'\n\nconst tiles = L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png', {\n attribution: '© ' + mapLink + ' Contributors',\n minZoom: 8,\n maxZoom: 18\n})\n\nfunction getColor (d) {\n return d > 300000000 ? '#4682B4'\n : d > 250000000 ? '#6495ED'\n : d > 100000000 ? '#1E90FF'\n : d > 50000000 ? '#B0C4DE'\n : d > 10000000 ? '#00BFFF'\n : d > 7500000 ? '#87CEEB'\n : d > 5000000 ? '#87CEFA'\n : '#ADD8E6'\n}\n\nfunction setStyle (feature) {\n return {\n fillColor: getColor(feature.properties.area),\n weight: 1,\n fillOpacity: 0.7,\n opacity: 0.80\n }\n}\n\n$.getJSON('./data/seattle-neighborhoods.geojson', function (data) {\n const geojson = L.geoJson(data, {\n style: setStyle,\n onEachFeature: function (feature, layer) {\n layer.bindPopup('<b>Name: </b>' + feature.properties.name + '<br><b>Area: </b>' + feature.properties.area)\n }\n })\n\n const map = L.map('mapid').fitBounds(geojson.getBounds())\n tiles.addTo(map)\n geojson.addTo(map)\n})\n"}],"Post":[{"title":"Map Preview","date":"2020-08-15T01:18:25.000Z","_content":"\nThe [Sublime Text 3](https://www.sublimetext.com/) Map Preview Plugin is now available in the [package control repository](https://packagecontrol.io/packages/MapPreview)! Preview map layer files in your web browser powered by [LeafletJS](https://leafletjs.com/). Currently features limited support for [GeoJSON](https://geojson.org/) and [TopoJSON](https://github.com/topojson/topojson/wiki) map file formats.\n\n\n\n## Installation\nInstall MapPreview via [Package Control](https://packagecontrol.io/packages/MapPreview)\n\n- Open Package Control: **Preferences > Package Control**\n- Select **Package Control: Install Package**\n- Type **MapPreview** into the search box and select the package to install it.\n\n## Preview Map in Browser\nWith an open valid GeoJSON or TopoJSON file in Sublime Text 3:\n\n- Right click and select **Map Preview > Preview in Browser**\n- Select **Map Preview: Preview in Browser** from the command palette.\n\n## Contribute\nThe plugin is open sourced on [GitHub](https://github.com/doneill/MapPreview) and open to any contributions.\n","source":"_posts/mappreview.md","raw":"---\ntitle: Map Preview\ndate: 2020-08-14 18:18:25\ntags:\n- sublime text\n- python\n- topojson\n- geojson\n- leaflet\n---\n\nThe [Sublime Text 3](https://www.sublimetext.com/) Map Preview Plugin is now available in the [package control repository](https://packagecontrol.io/packages/MapPreview)! Preview map layer files in your web browser powered by [LeafletJS](https://leafletjs.com/). Currently features limited support for [GeoJSON](https://geojson.org/) and [TopoJSON](https://github.com/topojson/topojson/wiki) map file formats.\n\n\n\n## Installation\nInstall MapPreview via [Package Control](https://packagecontrol.io/packages/MapPreview)\n\n- Open Package Control: **Preferences > Package Control**\n- Select **Package Control: Install Package**\n- Type **MapPreview** into the search box and select the package to install it.\n\n## Preview Map in Browser\nWith an open valid GeoJSON or TopoJSON file in Sublime Text 3:\n\n- Right click and select **Map Preview > Preview in Browser**\n- Select **Map Preview: Preview in Browser** from the command palette.\n\n## Contribute\nThe plugin is open sourced on [GitHub](https://github.com/doneill/MapPreview) and open to any contributions.\n","slug":"mappreview","published":1,"updated":"2020-08-15T01:18:25.000Z","comments":1,"layout":"post","photos":[],"link":"","_id":"ckz0jkmcw0008nvhwb5a12p26","content":"<p>The <a href=\"https://www.sublimetext.com/\">Sublime Text 3</a> Map Preview Plugin is now available in the <a href=\"https://packagecontrol.io/packages/MapPreview\">package control repository</a>! Preview map layer files in your web browser powered by <a href=\"https://leafletjs.com/\">LeafletJS</a>. Currently features limited support for <a href=\"https://geojson.org/\">GeoJSON</a> and <a href=\"https://github.com/topojson/topojson/wiki\">TopoJSON</a> map file formats.</p>\n<p><img src=\"/2020/08/14/mappreview/map-preview.png\" alt=\"Map Preview\"></p>\n<h2 id=\"Installation\"><a href=\"#Installation\" class=\"headerlink\" title=\"Installation\"></a>Installation</h2><p>Install MapPreview via <a href=\"https://packagecontrol.io/packages/MapPreview\">Package Control</a></p>\n<ul>\n<li>Open Package Control: <strong>Preferences > Package Control</strong></li>\n<li>Select <strong>Package Control: Install Package</strong></li>\n<li>Type <strong>MapPreview</strong> into the search box and select the package to install it.</li>\n</ul>\n<h2 id=\"Preview-Map-in-Browser\"><a href=\"#Preview-Map-in-Browser\" class=\"headerlink\" title=\"Preview Map in Browser\"></a>Preview Map in Browser</h2><p>With an open valid GeoJSON or TopoJSON file in Sublime Text 3:</p>\n<ul>\n<li>Right click and select <strong>Map Preview > Preview in Browser</strong></li>\n<li>Select <strong>Map Preview: Preview in Browser</strong> from the command palette.</li>\n</ul>\n<h2 id=\"Contribute\"><a href=\"#Contribute\" class=\"headerlink\" title=\"Contribute\"></a>Contribute</h2><p>The plugin is open sourced on <a href=\"https://github.com/doneill/MapPreview\">GitHub</a> and open to any contributions.</p>\n","site":{"data":{"projects":[{"name":"koop-provider-pg","url":"https://www.npmjs.com/package/koop-provider-pg","desc":"Koop provider to query PostGIS spatial data"},{"name":"Map Preview","url":"https://packagecontrol.io/packages/MapPreview","desc":"Preview map layer files in your web browser using Sublime Text 3"},{"name":"kotlin mulitplatform template","url":"https://github.com/doneill/kotlin-multiplatform-template","desc":"Kotlin MPP template to bootstrap Mobile/Web"}]}},"excerpt":"","more":"<p>The <a href=\"https://www.sublimetext.com/\">Sublime Text 3</a> Map Preview Plugin is now available in the <a href=\"https://packagecontrol.io/packages/MapPreview\">package control repository</a>! Preview map layer files in your web browser powered by <a href=\"https://leafletjs.com/\">LeafletJS</a>. Currently features limited support for <a href=\"https://geojson.org/\">GeoJSON</a> and <a href=\"https://github.com/topojson/topojson/wiki\">TopoJSON</a> map file formats.</p>\n<p><img src=\"/2020/08/14/mappreview/map-preview.png\" alt=\"Map Preview\"></p>\n<h2 id=\"Installation\"><a href=\"#Installation\" class=\"headerlink\" title=\"Installation\"></a>Installation</h2><p>Install MapPreview via <a href=\"https://packagecontrol.io/packages/MapPreview\">Package Control</a></p>\n<ul>\n<li>Open Package Control: <strong>Preferences > Package Control</strong></li>\n<li>Select <strong>Package Control: Install Package</strong></li>\n<li>Type <strong>MapPreview</strong> into the search box and select the package to install it.</li>\n</ul>\n<h2 id=\"Preview-Map-in-Browser\"><a href=\"#Preview-Map-in-Browser\" class=\"headerlink\" title=\"Preview Map in Browser\"></a>Preview Map in Browser</h2><p>With an open valid GeoJSON or TopoJSON file in Sublime Text 3:</p>\n<ul>\n<li>Right click and select <strong>Map Preview > Preview in Browser</strong></li>\n<li>Select <strong>Map Preview: Preview in Browser</strong> from the command palette.</li>\n</ul>\n<h2 id=\"Contribute\"><a href=\"#Contribute\" class=\"headerlink\" title=\"Contribute\"></a>Contribute</h2><p>The plugin is open sourced on <a href=\"https://github.com/doneill/MapPreview\">GitHub</a> and open to any contributions.</p>\n"},{"title":"Kotlin Multiplatform","date":"2019-12-07T01:24:25.000Z","_content":"\n\nMultiplatform projects is a feature in Kotlin that allows for sharing Kotlin code between iOS and Android. Android leverages the Kotlin/JVM and iOS uses Kotlin/Native which allows iOS apps to call Kotlin from Swift. This post will step us through getting an environment setup for Android and iOS development. A completed template project is available on [GitHub](https://github.com/doneill/kotlin-multiplatform-template).\n\n## Update 06/28/2020\n\nThese steps should still work if followed in order but the original repo on [GitHub](https://github.com/doneill/kotlin-multiplatform-template) has changed beyond just setting up the dev environment and can still be used as a guide to accompany this post. \n\n## Local Environment\n- [Android Studio](https://developer.android.com/studio)\n- Kotlin plugin 1.3.50 or higher should be installed in the IDE. \n- [Xcode](https://developer.apple.com/xcode/) \n\n## Create an Android Project\nCreate an empty activity project within Android Studio with **File > New > New Project** from the toolbar. Ensure that **Kotlin** is the selected language or ensure Kotlin support is checked (depending on version of Android Studio).\n\nKotlin native requires a recent version of Gradle, ensure that the **gradle/wrapper/gradle-wrapper.properties** file is grabbing 5.5.1 or higher with the following **distributionUrl**: \n\n```\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-5.5.1-all.zip\n```\n\nYou should be able to compile and run your new Android app. \n\n## Create an Shared Java Library module\nCreate a new module with **File > New > New Module** from the toolbar, then select **Java Library**. We will assume the library name is **lib**, but you can name it anything you want. \n\n### Setup folder structure for shared Java lib\nCreate the following subdirectories under your new **lib** module: \n\n- androidMain\n- commonMain\n- iosMain\n\nWith a corresponding **kotlin** folder underneath, resembling the structure below:\n\n```\n-- lib\n -- src\n -- androidMain\n -- kotlin\n -- commonMain\n -- kotlin\n -- iosMain\n -- kotlin\n```\n\n### Update shared lib build script\nWe will need to update the lib module build script to be a kotlin multiplatform lib. Rename the **lib/build.gradle** file to **lib/build.gradle.kts**, then remove all the contents, and replace with the following code: \n\n```kotlin\nimport org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget\n\nplugins {\n kotlin(\"multiplatform\")\n}\n\nkotlin {\n //select iOS target platform depending on the Xcode environment variables\n val iOSTarget: (String, KotlinNativeTarget.() -> Unit) -> KotlinNativeTarget =\n if (System.getenv(\"SDK_NAME\")?.startsWith(\"iphoneos\") == true)\n ::iosArm64\n else\n ::iosX64\n\n iOSTarget(\"ios\") {\n binaries {\n framework {\n baseName = \"SharedCode\"\n }\n }\n }\n\n jvm(\"android\")\n\n sourceSets[\"commonMain\"].dependencies {\n implementation(\"org.jetbrains.kotlin:kotlin-stdlib-common\")\n }\n\n sourceSets[\"androidMain\"].dependencies {\n implementation(\"org.jetbrains.kotlin:kotlin-stdlib\")\n }\n}\n\nval packForXcode by tasks.creating(Sync::class) {\n val targetDir = File(buildDir, \"xcode-frameworks\")\n\n /// selecting the right configuration for the iOS\n /// framework depending on the environment\n /// variables set by Xcode build\n val mode = System.getenv(\"CONFIGURATION\") ?: \"DEBUG\"\n val framework = kotlin.targets\n .getByName<KotlinNativeTarget>(\"ios\")\n .binaries.getFramework(mode)\n inputs.property(\"mode\", mode)\n dependsOn(framework.linkTask)\n\n from({ framework.outputDirectory })\n into(targetDir)\n\n /// generate a helpful ./gradlew wrapper with embedded Java path\n doLast {\n val gradlew = File(targetDir, \"gradlew\")\n gradlew.writeText(\"#!/bin/bash\\n\"\n + \"export 'JAVA_HOME=${System.getProperty(\"java.home\")}'\\n\"\n + \"cd '${rootProject.rootDir}'\\n\"\n + \"./gradlew \\$@\\n\")\n gradlew.setExecutable(true)\n }\n}\n\ntasks.getByName(\"build\").dependsOn(packForXcode)\n```\n\nThe update build script uses `kotlin-multiplatform` plugin and defines several targets. It also defines a `packForXcode` task which configures a framework lib to link in our iOS app we will create in a later step. \n\n## Implement shared lib module\nCreate a new package and kotlin source file under **lib/src/commonMain/DateHelper.kt**. Add the following code to get the current date as `String`. \n\n```kotlin\npackage com.yourdomain.platform\n\nexpect fun getCurrentDate(): String\n\nfun getDate(): String {\n return \"Today's Date is ${getCurrentDate()}\"\n}\n```\n\nThe method dependent on platform implementation is marked as `expect`. The method called by Android and iOS app clients is `getDate()`. \n\nThe common lib implementation is done in **androidMain** and **iosMain** respectively. Create the same package as **commonMain** with a new kotlin source file under **lib/src/androidMain/DateHelperAnd.kt** and **lib/src/iosMain/DateHelperIos.kt**\n\nAdd the following for **DateHelperAnd.kt**\n\n```kotlin\npackage com.yourdomain.platform\n\nimport java.util.Date\n\nactual fun getCurrentDate(): String = Date().toString()\n```\n\nAdd the following for **DateHelperIos.kt**\n\n```kotlin\npackage com.yourdomain.platform\n\nimport platform.Foundation.NSDate\n\nactual fun getCurrentDate() = NSDate().toString()\n```\n\nThe implementation of the `expect` methods are marked as `actual`. Kotlin/Native compiler comes with a set of pre-imported frameworks, so we can use the `NSDate`. Objective C and Swift interop covered in more detail [here](https://kotlinlang.org/docs/reference/native/objc_interop.html).\n\n## Link shared module in Android\nIn order for our Android app to depend on the shared lib we must link it as a dependency. Open the **app/build.gradle** file and add the following under **dependencies** block:\n\n```\n\ndependencies {\n implementation project (':lib')\n ... \n}\n```\n\n## Update the Android app\nUsing the default `TextView` added when we generated an Android project, let's assign an `id` to the `TextView` in the activity so we can access it from code. Open **app/src/main/res/layout/activity_main.xml** and add the `id`: \n\n```xml\n<TextView android:id=\"@+id/date_view\"\n ...\n />\n```\n\nNow we can add the following line of code to the `MainActivity` class in **/app/src/main/java/[package]/MainActivity.kt** file to the end of the `onCreate` method: \n\n```kotlin\nfindViewById<TextView>(R.id.date_view).text = getDate()\n```\n\nAt this point you should be able to run the Android app and see the current date.\n\n## Create iOS project\nFire up Xcode and create a new **Single View App** with **File > New > Project**. Create a project name, e.g. **ios-app**, and set the directory inside of the project directory created with Android Studio. Your resulting project structure should look similar to this: \n\n```\n-- Project\n -- app\n -- lib\n -- iosApp\n```\n\n\n## Generate shared framework and link to project\nWe need to build the framework initially before we can link it to our ios project using the `build` task from our common lib module. Open a terminal and cd into the **lib** module folder and run the following: \n\n```\n$ ./gradlew clean packForXcode --info\n```\n\nThis will run the `packForXCode` task we defined and configure the framework inside of the **lib/build/xcode-frameworks** folder. \n\n### Add embedded libs\nSelect the **ios-app** project inside xcode file view to open the project properties. Under the **General** tab scroll down to **Frameworks, Libraries, and Embedded Content** and click on the `+` button to add the framework. Select the **SharedContent.framework** and click **Add**. \n\n### Update build settings\nWith the project properties open, select the **Build Settings** tab and search for **EFramework Search Paths** and update the path to `$(SRCROOT)/../lib/build/xcode-frameworks`\n\n### Create new run script\nTo keep the framework updated with new builds, select the **Build Rules** tab and add a new run script phase. Add the following to the shell script: \n\n```\ncd \"$SRCROOT/../lib/build/xcode-frameworks\"\n./gradlew :lib:packForXCode -PXCODE_CONFIGURATION=${CONFIGURATION}\n```\n\nThis will keep the framework up to date on successive builds. \n\n## Update the iOS App\nThe iOS app will use the common lib from Kotlin. Open up the **ViewController.swift** file and add the following import: \n\n```swift\n// import the framework\nimport main\n```\n\nNow append the following to the `viewDidLoad()` method: \n\n```swift\nlet date = DateHelperKt.getDate()\n\nlet label = UILabel(frame: CGRect(x: 0, y: 0, width: 230, height: 42))\nlabel.center = CGPoint(x: 160, y: 285)\nlabel.textAlignment = .center\nlabel.font = label.font.withSize(16)\nlabel.textColor = UIColor.black\nlabel.numberOfLines = 0\nlabel.lineBreakMode = .byWordWrapping\nlabel.text = date\nview.addSubview(label)\n```\n\nTake note of the `DateHelperKt.getDate` method call to Kotlin. At this point you should be able to run the iOS app and see the current date. \n\n## References\nThis is a basic example of setting up code sharing with Kotlin between iOS and Android, below are some references to documentation and libraries: \n\n- [Kotlin/Native interoperability with Swift/Objective-C](https://kotlinlang.org/docs/reference/native/objc_interop.html)\n- [Multiplatform Programming](https://kotlinlang.org/docs/reference/multiplatform.html)\n- [kotlinx.coroutines](https://github.com/Kotlin/kotlinx.coroutines/blob/master/README.md)\n- [kotlinx-io](https://github.com/Kotlin/kotlinx-io)\n- [kotlinx.serialization](https://github.com/Kotlin/kotlinx.serialization)\n- [Ktor](https://ktor.io/)\n- [Ktor HTTP client](https://ktor.io/clients/index.html)\n","source":"_posts/kotlin-mulitplatform.md","raw":"---\ntitle: Kotlin Multiplatform\ndate: 2019-12-06 17:24:25\ntags:\n- kotlin\n- multiplatform\n---\n\n\nMultiplatform projects is a feature in Kotlin that allows for sharing Kotlin code between iOS and Android. Android leverages the Kotlin/JVM and iOS uses Kotlin/Native which allows iOS apps to call Kotlin from Swift. This post will step us through getting an environment setup for Android and iOS development. A completed template project is available on [GitHub](https://github.com/doneill/kotlin-multiplatform-template).\n\n## Update 06/28/2020\n\nThese steps should still work if followed in order but the original repo on [GitHub](https://github.com/doneill/kotlin-multiplatform-template) has changed beyond just setting up the dev environment and can still be used as a guide to accompany this post. \n\n## Local Environment\n- [Android Studio](https://developer.android.com/studio)\n- Kotlin plugin 1.3.50 or higher should be installed in the IDE. \n- [Xcode](https://developer.apple.com/xcode/) \n\n## Create an Android Project\nCreate an empty activity project within Android Studio with **File > New > New Project** from the toolbar. Ensure that **Kotlin** is the selected language or ensure Kotlin support is checked (depending on version of Android Studio).\n\nKotlin native requires a recent version of Gradle, ensure that the **gradle/wrapper/gradle-wrapper.properties** file is grabbing 5.5.1 or higher with the following **distributionUrl**: \n\n```\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-5.5.1-all.zip\n```\n\nYou should be able to compile and run your new Android app. \n\n## Create an Shared Java Library module\nCreate a new module with **File > New > New Module** from the toolbar, then select **Java Library**. We will assume the library name is **lib**, but you can name it anything you want. \n\n### Setup folder structure for shared Java lib\nCreate the following subdirectories under your new **lib** module: \n\n- androidMain\n- commonMain\n- iosMain\n\nWith a corresponding **kotlin** folder underneath, resembling the structure below:\n\n```\n-- lib\n -- src\n -- androidMain\n -- kotlin\n -- commonMain\n -- kotlin\n -- iosMain\n -- kotlin\n```\n\n### Update shared lib build script\nWe will need to update the lib module build script to be a kotlin multiplatform lib. Rename the **lib/build.gradle** file to **lib/build.gradle.kts**, then remove all the contents, and replace with the following code: \n\n```kotlin\nimport org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget\n\nplugins {\n kotlin(\"multiplatform\")\n}\n\nkotlin {\n //select iOS target platform depending on the Xcode environment variables\n val iOSTarget: (String, KotlinNativeTarget.() -> Unit) -> KotlinNativeTarget =\n if (System.getenv(\"SDK_NAME\")?.startsWith(\"iphoneos\") == true)\n ::iosArm64\n else\n ::iosX64\n\n iOSTarget(\"ios\") {\n binaries {\n framework {\n baseName = \"SharedCode\"\n }\n }\n }\n\n jvm(\"android\")\n\n sourceSets[\"commonMain\"].dependencies {\n implementation(\"org.jetbrains.kotlin:kotlin-stdlib-common\")\n }\n\n sourceSets[\"androidMain\"].dependencies {\n implementation(\"org.jetbrains.kotlin:kotlin-stdlib\")\n }\n}\n\nval packForXcode by tasks.creating(Sync::class) {\n val targetDir = File(buildDir, \"xcode-frameworks\")\n\n /// selecting the right configuration for the iOS\n /// framework depending on the environment\n /// variables set by Xcode build\n val mode = System.getenv(\"CONFIGURATION\") ?: \"DEBUG\"\n val framework = kotlin.targets\n .getByName<KotlinNativeTarget>(\"ios\")\n .binaries.getFramework(mode)\n inputs.property(\"mode\", mode)\n dependsOn(framework.linkTask)\n\n from({ framework.outputDirectory })\n into(targetDir)\n\n /// generate a helpful ./gradlew wrapper with embedded Java path\n doLast {\n val gradlew = File(targetDir, \"gradlew\")\n gradlew.writeText(\"#!/bin/bash\\n\"\n + \"export 'JAVA_HOME=${System.getProperty(\"java.home\")}'\\n\"\n + \"cd '${rootProject.rootDir}'\\n\"\n + \"./gradlew \\$@\\n\")\n gradlew.setExecutable(true)\n }\n}\n\ntasks.getByName(\"build\").dependsOn(packForXcode)\n```\n\nThe update build script uses `kotlin-multiplatform` plugin and defines several targets. It also defines a `packForXcode` task which configures a framework lib to link in our iOS app we will create in a later step. \n\n## Implement shared lib module\nCreate a new package and kotlin source file under **lib/src/commonMain/DateHelper.kt**. Add the following code to get the current date as `String`. \n\n```kotlin\npackage com.yourdomain.platform\n\nexpect fun getCurrentDate(): String\n\nfun getDate(): String {\n return \"Today's Date is ${getCurrentDate()}\"\n}\n```\n\nThe method dependent on platform implementation is marked as `expect`. The method called by Android and iOS app clients is `getDate()`. \n\nThe common lib implementation is done in **androidMain** and **iosMain** respectively. Create the same package as **commonMain** with a new kotlin source file under **lib/src/androidMain/DateHelperAnd.kt** and **lib/src/iosMain/DateHelperIos.kt**\n\nAdd the following for **DateHelperAnd.kt**\n\n```kotlin\npackage com.yourdomain.platform\n\nimport java.util.Date\n\nactual fun getCurrentDate(): String = Date().toString()\n```\n\nAdd the following for **DateHelperIos.kt**\n\n```kotlin\npackage com.yourdomain.platform\n\nimport platform.Foundation.NSDate\n\nactual fun getCurrentDate() = NSDate().toString()\n```\n\nThe implementation of the `expect` methods are marked as `actual`. Kotlin/Native compiler comes with a set of pre-imported frameworks, so we can use the `NSDate`. Objective C and Swift interop covered in more detail [here](https://kotlinlang.org/docs/reference/native/objc_interop.html).\n\n## Link shared module in Android\nIn order for our Android app to depend on the shared lib we must link it as a dependency. Open the **app/build.gradle** file and add the following under **dependencies** block:\n\n```\n\ndependencies {\n implementation project (':lib')\n ... \n}\n```\n\n## Update the Android app\nUsing the default `TextView` added when we generated an Android project, let's assign an `id` to the `TextView` in the activity so we can access it from code. Open **app/src/main/res/layout/activity_main.xml** and add the `id`: \n\n```xml\n<TextView android:id=\"@+id/date_view\"\n ...\n />\n```\n\nNow we can add the following line of code to the `MainActivity` class in **/app/src/main/java/[package]/MainActivity.kt** file to the end of the `onCreate` method: \n\n```kotlin\nfindViewById<TextView>(R.id.date_view).text = getDate()\n```\n\nAt this point you should be able to run the Android app and see the current date.\n\n## Create iOS project\nFire up Xcode and create a new **Single View App** with **File > New > Project**. Create a project name, e.g. **ios-app**, and set the directory inside of the project directory created with Android Studio. Your resulting project structure should look similar to this: \n\n```\n-- Project\n -- app\n -- lib\n -- iosApp\n```\n\n\n## Generate shared framework and link to project\nWe need to build the framework initially before we can link it to our ios project using the `build` task from our common lib module. Open a terminal and cd into the **lib** module folder and run the following: \n\n```\n$ ./gradlew clean packForXcode --info\n```\n\nThis will run the `packForXCode` task we defined and configure the framework inside of the **lib/build/xcode-frameworks** folder. \n\n### Add embedded libs\nSelect the **ios-app** project inside xcode file view to open the project properties. Under the **General** tab scroll down to **Frameworks, Libraries, and Embedded Content** and click on the `+` button to add the framework. Select the **SharedContent.framework** and click **Add**. \n\n### Update build settings\nWith the project properties open, select the **Build Settings** tab and search for **EFramework Search Paths** and update the path to `$(SRCROOT)/../lib/build/xcode-frameworks`\n\n### Create new run script\nTo keep the framework updated with new builds, select the **Build Rules** tab and add a new run script phase. Add the following to the shell script: \n\n```\ncd \"$SRCROOT/../lib/build/xcode-frameworks\"\n./gradlew :lib:packForXCode -PXCODE_CONFIGURATION=${CONFIGURATION}\n```\n\nThis will keep the framework up to date on successive builds. \n\n## Update the iOS App\nThe iOS app will use the common lib from Kotlin. Open up the **ViewController.swift** file and add the following import: \n\n```swift\n// import the framework\nimport main\n```\n\nNow append the following to the `viewDidLoad()` method: \n\n```swift\nlet date = DateHelperKt.getDate()\n\nlet label = UILabel(frame: CGRect(x: 0, y: 0, width: 230, height: 42))\nlabel.center = CGPoint(x: 160, y: 285)\nlabel.textAlignment = .center\nlabel.font = label.font.withSize(16)\nlabel.textColor = UIColor.black\nlabel.numberOfLines = 0\nlabel.lineBreakMode = .byWordWrapping\nlabel.text = date\nview.addSubview(label)\n```\n\nTake note of the `DateHelperKt.getDate` method call to Kotlin. At this point you should be able to run the iOS app and see the current date. \n\n## References\nThis is a basic example of setting up code sharing with Kotlin between iOS and Android, below are some references to documentation and libraries: \n\n- [Kotlin/Native interoperability with Swift/Objective-C](https://kotlinlang.org/docs/reference/native/objc_interop.html)\n- [Multiplatform Programming](https://kotlinlang.org/docs/reference/multiplatform.html)\n- [kotlinx.coroutines](https://github.com/Kotlin/kotlinx.coroutines/blob/master/README.md)\n- [kotlinx-io](https://github.com/Kotlin/kotlinx-io)\n- [kotlinx.serialization](https://github.com/Kotlin/kotlinx.serialization)\n- [Ktor](https://ktor.io/)\n- [Ktor HTTP client](https://ktor.io/clients/index.html)\n","slug":"kotlin-mulitplatform","published":1,"updated":"2019-12-07T01:24:25.000Z","comments":1,"layout":"post","photos":[],"link":"","_id":"ckz0jkmcx000anvhwhoexbyym","content":"<p>Multiplatform projects is a feature in Kotlin that allows for sharing Kotlin code between iOS and Android. Android leverages the Kotlin/JVM and iOS uses Kotlin/Native which allows iOS apps to call Kotlin from Swift. This post will step us through getting an environment setup for Android and iOS development. A completed template project is available on <a href=\"https://github.com/doneill/kotlin-multiplatform-template\">GitHub</a>.</p>\n<h2 id=\"Update-06-28-2020\"><a href=\"#Update-06-28-2020\" class=\"headerlink\" title=\"Update 06/28/2020\"></a>Update 06/28/2020</h2><p>These steps should still work if followed in order but the original repo on <a href=\"https://github.com/doneill/kotlin-multiplatform-template\">GitHub</a> has changed beyond just setting up the dev environment and can still be used as a guide to accompany this post. </p>\n<h2 id=\"Local-Environment\"><a href=\"#Local-Environment\" class=\"headerlink\" title=\"Local Environment\"></a>Local Environment</h2><ul>\n<li><a href=\"https://developer.android.com/studio\">Android Studio</a></li>\n<li>Kotlin plugin 1.3.50 or higher should be installed in the IDE. </li>\n<li><a href=\"https://developer.apple.com/xcode/\">Xcode</a> </li>\n</ul>\n<h2 id=\"Create-an-Android-Project\"><a href=\"#Create-an-Android-Project\" class=\"headerlink\" title=\"Create an Android Project\"></a>Create an Android Project</h2><p>Create an empty activity project within Android Studio with <strong>File > New > New Project</strong> from the toolbar. Ensure that <strong>Kotlin</strong> is the selected language or ensure Kotlin support is checked (depending on version of Android Studio).</p>\n<p>Kotlin native requires a recent version of Gradle, ensure that the <strong>gradle/wrapper/gradle-wrapper.properties</strong> file is grabbing 5.5.1 or higher with the following <strong>distributionUrl</strong>: </p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">distributionUrl=https\\://services.gradle.org/distributions/gradle-5.5.1-all.zip</span><br></pre></td></tr></table></figure>\n\n<p>You should be able to compile and run your new Android app. </p>\n<h2 id=\"Create-an-Shared-Java-Library-module\"><a href=\"#Create-an-Shared-Java-Library-module\" class=\"headerlink\" title=\"Create an Shared Java Library module\"></a>Create an Shared Java Library module</h2><p>Create a new module with <strong>File > New > New Module</strong> from the toolbar, then select <strong>Java Library</strong>. We will assume the library name is <strong>lib</strong>, but you can name it anything you want. </p>\n<h3 id=\"Setup-folder-structure-for-shared-Java-lib\"><a href=\"#Setup-folder-structure-for-shared-Java-lib\" class=\"headerlink\" title=\"Setup folder structure for shared Java lib\"></a>Setup folder structure for shared Java lib</h3><p>Create the following subdirectories under your new <strong>lib</strong> module: </p>\n<ul>\n<li>androidMain</li>\n<li>commonMain</li>\n<li>iosMain</li>\n</ul>\n<p>With a corresponding <strong>kotlin</strong> folder underneath, resembling the structure below:</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br><span class=\"line\">8</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">-- lib</span><br><span class=\"line\"> -- src</span><br><span class=\"line\"> -- androidMain</span><br><span class=\"line\"> -- kotlin</span><br><span class=\"line\"> -- commonMain</span><br><span class=\"line\"> -- kotlin</span><br><span class=\"line\"> -- iosMain</span><br><span class=\"line\"> -- kotlin</span><br></pre></td></tr></table></figure>\n\n<h3 id=\"Update-shared-lib-build-script\"><a href=\"#Update-shared-lib-build-script\" class=\"headerlink\" title=\"Update shared lib build script\"></a>Update shared lib build script</h3><p>We will need to update the lib module build script to be a kotlin multiplatform lib. Rename the <strong>lib/build.gradle</strong> file to <strong>lib/build.gradle.kts</strong>, then remove all the contents, and replace with the following code: </p>\n<figure class=\"highlight kotlin\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br><span class=\"line\">8</span><br><span class=\"line\">9</span><br><span class=\"line\">10</span><br><span class=\"line\">11</span><br><span class=\"line\">12</span><br><span class=\"line\">13</span><br><span class=\"line\">14</span><br><span class=\"line\">15</span><br><span class=\"line\">16</span><br><span class=\"line\">17</span><br><span class=\"line\">18</span><br><span class=\"line\">19</span><br><span class=\"line\">20</span><br><span class=\"line\">21</span><br><span class=\"line\">22</span><br><span class=\"line\">23</span><br><span class=\"line\">24</span><br><span class=\"line\">25</span><br><span class=\"line\">26</span><br><span class=\"line\">27</span><br><span class=\"line\">28</span><br><span class=\"line\">29</span><br><span class=\"line\">30</span><br><span class=\"line\">31</span><br><span class=\"line\">32</span><br><span class=\"line\">33</span><br><span class=\"line\">34</span><br><span class=\"line\">35</span><br><span class=\"line\">36</span><br><span class=\"line\">37</span><br><span class=\"line\">38</span><br><span class=\"line\">39</span><br><span class=\"line\">40</span><br><span class=\"line\">41</span><br><span class=\"line\">42</span><br><span class=\"line\">43</span><br><span class=\"line\">44</span><br><span class=\"line\">45</span><br><span class=\"line\">46</span><br><span class=\"line\">47</span><br><span class=\"line\">48</span><br><span class=\"line\">49</span><br><span class=\"line\">50</span><br><span class=\"line\">51</span><br><span class=\"line\">52</span><br><span class=\"line\">53</span><br><span class=\"line\">54</span><br><span class=\"line\">55</span><br><span class=\"line\">56</span><br><span class=\"line\">57</span><br><span class=\"line\">58</span><br><span class=\"line\">59</span><br><span class=\"line\">60</span><br><span class=\"line\">61</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">import</span> org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget</span><br><span class=\"line\"></span><br><span class=\"line\">plugins {</span><br><span class=\"line\"> kotlin(<span class=\"string\">"multiplatform"</span>)</span><br><span class=\"line\">}</span><br><span class=\"line\"></span><br><span class=\"line\">kotlin {</span><br><span class=\"line\"> <span class=\"comment\">//select iOS target platform depending on the Xcode environment variables</span></span><br><span class=\"line\"> <span class=\"keyword\">val</span> iOSTarget: (String, KotlinNativeTarget.() -> <span class=\"built_in\">Unit</span>) -> KotlinNativeTarget =</span><br><span class=\"line\"> <span class=\"keyword\">if</span> (System.getenv(<span class=\"string\">"SDK_NAME"</span>)?.startsWith(<span class=\"string\">"iphoneos"</span>) == <span class=\"literal\">true</span>)</span><br><span class=\"line\"> ::iosArm64</span><br><span class=\"line\"> <span class=\"keyword\">else</span></span><br><span class=\"line\"> ::iosX64</span><br><span class=\"line\"></span><br><span class=\"line\"> iOSTarget(<span class=\"string\">"ios"</span>) {</span><br><span class=\"line\"> binaries {</span><br><span class=\"line\"> framework {</span><br><span class=\"line\"> baseName = <span class=\"string\">"SharedCode"</span></span><br><span class=\"line\"> }</span><br><span class=\"line\"> }</span><br><span class=\"line\"> }</span><br><span class=\"line\"></span><br><span class=\"line\"> jvm(<span class=\"string\">"android"</span>)</span><br><span class=\"line\"></span><br><span class=\"line\"> sourceSets[<span class=\"string\">"commonMain"</span>].dependencies {</span><br><span class=\"line\"> implementation(<span class=\"string\">"org.jetbrains.kotlin:kotlin-stdlib-common"</span>)</span><br><span class=\"line\"> }</span><br><span class=\"line\"></span><br><span class=\"line\"> sourceSets[<span class=\"string\">"androidMain"</span>].dependencies {</span><br><span class=\"line\"> implementation(<span class=\"string\">"org.jetbrains.kotlin:kotlin-stdlib"</span>)</span><br><span class=\"line\"> }</span><br><span class=\"line\">}</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"keyword\">val</span> packForXcode <span class=\"keyword\">by</span> tasks.creating(Sync::<span class=\"class\"><span class=\"keyword\">class</span>) </span>{</span><br><span class=\"line\"> <span class=\"keyword\">val</span> targetDir = File(buildDir, <span class=\"string\">"xcode-frameworks"</span>)</span><br><span class=\"line\"></span><br><span class=\"line\"> <span class=\"comment\">/// selecting the right configuration for the iOS</span></span><br><span class=\"line\"> <span class=\"comment\">/// framework depending on the environment</span></span><br><span class=\"line\"> <span class=\"comment\">/// variables set by Xcode build</span></span><br><span class=\"line\"> <span class=\"keyword\">val</span> mode = System.getenv(<span class=\"string\">"CONFIGURATION"</span>) ?: <span class=\"string\">"DEBUG"</span></span><br><span class=\"line\"> <span class=\"keyword\">val</span> framework = kotlin.targets</span><br><span class=\"line\"> .getByName<KotlinNativeTarget>(<span class=\"string\">"ios"</span>)</span><br><span class=\"line\"> .binaries.getFramework(mode)</span><br><span class=\"line\"> inputs.property(<span class=\"string\">"mode"</span>, mode)</span><br><span class=\"line\"> dependsOn(framework.linkTask)</span><br><span class=\"line\"></span><br><span class=\"line\"> from({ framework.outputDirectory })</span><br><span class=\"line\"> into(targetDir)</span><br><span class=\"line\"></span><br><span class=\"line\"> <span class=\"comment\">/// generate a helpful ./gradlew wrapper with embedded Java path</span></span><br><span class=\"line\"> doLast {</span><br><span class=\"line\"> <span class=\"keyword\">val</span> gradlew = File(targetDir, <span class=\"string\">"gradlew"</span>)</span><br><span class=\"line\"> gradlew.writeText(<span class=\"string\">"#!/bin/bash\\n"</span></span><br><span class=\"line\"> + <span class=\"string\">"export 'JAVA_HOME=<span class=\"subst\">${System.getProperty(<span class=\"string\">"java.home"</span>)}</span>'\\n"</span></span><br><span class=\"line\"> + <span class=\"string\">"cd '<span class=\"subst\">${rootProject.rootDir}</span>'\\n"</span></span><br><span class=\"line\"> + <span class=\"string\">"./gradlew \\$@\\n"</span>)</span><br><span class=\"line\"> gradlew.setExecutable(<span class=\"literal\">true</span>)</span><br><span class=\"line\"> }</span><br><span class=\"line\">}</span><br><span class=\"line\"></span><br><span class=\"line\">tasks.getByName(<span class=\"string\">"build"</span>).dependsOn(packForXcode)</span><br></pre></td></tr></table></figure>\n\n<p>The update build script uses <code>kotlin-multiplatform</code> plugin and defines several targets. It also defines a <code>packForXcode</code> task which configures a framework lib to link in our iOS app we will create in a later step. </p>\n<h2 id=\"Implement-shared-lib-module\"><a href=\"#Implement-shared-lib-module\" class=\"headerlink\" title=\"Implement shared lib module\"></a>Implement shared lib module</h2><p>Create a new package and kotlin source file under <strong>lib/src/commonMain/DateHelper.kt</strong>. Add the following code to get the current date as <code>String</code>. </p>\n<figure class=\"highlight kotlin\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">package</span> com.yourdomain.platform</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"keyword\">expect</span> <span class=\"function\"><span class=\"keyword\">fun</span> <span class=\"title\">getCurrentDate</span><span class=\"params\">()</span></span>: String</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"function\"><span class=\"keyword\">fun</span> <span class=\"title\">getDate</span><span class=\"params\">()</span></span>: String {</span><br><span class=\"line\"> <span class=\"keyword\">return</span> <span class=\"string\">"Today's Date is <span class=\"subst\">${getCurrentDate()}</span>"</span></span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n\n<p>The method dependent on platform implementation is marked as <code>expect</code>. The method called by Android and iOS app clients is <code>getDate()</code>. </p>\n<p>The common lib implementation is done in <strong>androidMain</strong> and <strong>iosMain</strong> respectively. Create the same package as <strong>commonMain</strong> with a new kotlin source file under <strong>lib/src/androidMain/DateHelperAnd.kt</strong> and <strong>lib/src/iosMain/DateHelperIos.kt</strong></p>\n<p>Add the following for <strong>DateHelperAnd.kt</strong></p>\n<figure class=\"highlight kotlin\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">package</span> com.yourdomain.platform</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"keyword\">import</span> java.util.Date</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"keyword\">actual</span> <span class=\"function\"><span class=\"keyword\">fun</span> <span class=\"title\">getCurrentDate</span><span class=\"params\">()</span></span>: String = Date().toString()</span><br></pre></td></tr></table></figure>\n\n<p>Add the following for <strong>DateHelperIos.kt</strong></p>\n<figure class=\"highlight kotlin\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">package</span> com.yourdomain.platform</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"keyword\">import</span> platform.Foundation.NSDate</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"keyword\">actual</span> <span class=\"function\"><span class=\"keyword\">fun</span> <span class=\"title\">getCurrentDate</span><span class=\"params\">()</span></span> = NSDate().toString()</span><br></pre></td></tr></table></figure>\n\n<p>The implementation of the <code>expect</code> methods are marked as <code>actual</code>. Kotlin/Native compiler comes with a set of pre-imported frameworks, so we can use the <code>NSDate</code>. Objective C and Swift interop covered in more detail <a href=\"https://kotlinlang.org/docs/reference/native/objc_interop.html\">here</a>.</p>\n<h2 id=\"Link-shared-module-in-Android\"><a href=\"#Link-shared-module-in-Android\" class=\"headerlink\" title=\"Link shared module in Android\"></a>Link shared module in Android</h2><p>In order for our Android app to depend on the shared lib we must link it as a dependency. Open the <strong>app/build.gradle</strong> file and add the following under <strong>dependencies</strong> block:</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"></span><br><span class=\"line\">dependencies {</span><br><span class=\"line\"> implementation project (':lib')</span><br><span class=\"line\"> ... </span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n\n<h2 id=\"Update-the-Android-app\"><a href=\"#Update-the-Android-app\" class=\"headerlink\" title=\"Update the Android app\"></a>Update the Android app</h2><p>Using the default <code>TextView</code> added when we generated an Android project, let’s assign an <code>id</code> to the <code>TextView</code> in the activity so we can access it from code. Open <strong>app/src/main/res/layout/activity_main.xml</strong> and add the <code>id</code>: </p>\n<figure class=\"highlight xml\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"tag\"><<span class=\"name\">TextView</span> <span class=\"attr\">android:id</span>=<span class=\"string\">"@+id/date_view"</span></span></span><br><span class=\"line\"><span class=\"tag\"> <span class=\"attr\">...</span></span></span><br><span class=\"line\"><span class=\"tag\"> /></span></span><br></pre></td></tr></table></figure>\n\n<p>Now we can add the following line of code to the <code>MainActivity</code> class in <strong>/app/src/main/java/[package]/MainActivity.kt</strong> file to the end of the <code>onCreate</code> method: </p>\n<figure class=\"highlight kotlin\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">findViewById<TextView>(R.id.date_view).text = getDate()</span><br></pre></td></tr></table></figure>\n\n<p>At this point you should be able to run the Android app and see the current date.</p>\n<h2 id=\"Create-iOS-project\"><a href=\"#Create-iOS-project\" class=\"headerlink\" title=\"Create iOS project\"></a>Create iOS project</h2><p>Fire up Xcode and create a new <strong>Single View App</strong> with <strong>File > New > Project</strong>. Create a project name, e.g. <strong>ios-app</strong>, and set the directory inside of the project directory created with Android Studio. Your resulting project structure should look similar to this: </p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">-- Project</span><br><span class=\"line\"> -- app</span><br><span class=\"line\"> -- lib</span><br><span class=\"line\"> -- iosApp</span><br></pre></td></tr></table></figure>\n\n\n<h2 id=\"Generate-shared-framework-and-link-to-project\"><a href=\"#Generate-shared-framework-and-link-to-project\" class=\"headerlink\" title=\"Generate shared framework and link to project\"></a>Generate shared framework and link to project</h2><p>We need to build the framework initially before we can link it to our ios project using the <code>build</code> task from our common lib module. Open a terminal and cd into the <strong>lib</strong> module folder and run the following: </p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">$ ./gradlew clean packForXcode --info</span><br></pre></td></tr></table></figure>\n\n<p>This will run the <code>packForXCode</code> task we defined and configure the framework inside of the <strong>lib/build/xcode-frameworks</strong> folder. </p>\n<h3 id=\"Add-embedded-libs\"><a href=\"#Add-embedded-libs\" class=\"headerlink\" title=\"Add embedded libs\"></a>Add embedded libs</h3><p>Select the <strong>ios-app</strong> project inside xcode file view to open the project properties. Under the <strong>General</strong> tab scroll down to <strong>Frameworks, Libraries, and Embedded Content</strong> and click on the <code>+</code> button to add the framework. Select the <strong>SharedContent.framework</strong> and click <strong>Add</strong>. </p>\n<h3 id=\"Update-build-settings\"><a href=\"#Update-build-settings\" class=\"headerlink\" title=\"Update build settings\"></a>Update build settings</h3><p>With the project properties open, select the <strong>Build Settings</strong> tab and search for <strong>EFramework Search Paths</strong> and update the path to <code>$(SRCROOT)/../lib/build/xcode-frameworks</code></p>\n<h3 id=\"Create-new-run-script\"><a href=\"#Create-new-run-script\" class=\"headerlink\" title=\"Create new run script\"></a>Create new run script</h3><p>To keep the framework updated with new builds, select the <strong>Build Rules</strong> tab and add a new run script phase. Add the following to the shell script: </p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">cd "$SRCROOT/../lib/build/xcode-frameworks"</span><br><span class=\"line\">./gradlew :lib:packForXCode -PXCODE_CONFIGURATION=${CONFIGURATION}</span><br></pre></td></tr></table></figure>\n\n<p>This will keep the framework up to date on successive builds. </p>\n<h2 id=\"Update-the-iOS-App\"><a href=\"#Update-the-iOS-App\" class=\"headerlink\" title=\"Update the iOS App\"></a>Update the iOS App</h2><p>The iOS app will use the common lib from Kotlin. Open up the <strong>ViewController.swift</strong> file and add the following import: </p>\n<figure class=\"highlight swift\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"comment\">// import the framework</span></span><br><span class=\"line\"><span class=\"keyword\">import</span> main</span><br></pre></td></tr></table></figure>\n\n<p>Now append the following to the <code>viewDidLoad()</code> method: </p>\n<figure class=\"highlight swift\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br><span class=\"line\">8</span><br><span class=\"line\">9</span><br><span class=\"line\">10</span><br><span class=\"line\">11</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">let</span> date <span class=\"operator\">=</span> <span class=\"type\">DateHelperKt</span>.getDate()</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"keyword\">let</span> label <span class=\"operator\">=</span> <span class=\"type\">UILabel</span>(frame: <span class=\"type\">CGRect</span>(x: <span class=\"number\">0</span>, y: <span class=\"number\">0</span>, width: <span class=\"number\">230</span>, height: <span class=\"number\">42</span>))</span><br><span class=\"line\">label.center <span class=\"operator\">=</span> <span class=\"type\">CGPoint</span>(x: <span class=\"number\">160</span>, y: <span class=\"number\">285</span>)</span><br><span class=\"line\">label.textAlignment <span class=\"operator\">=</span> .center</span><br><span class=\"line\">label.font <span class=\"operator\">=</span> label.font.withSize(<span class=\"number\">16</span>)</span><br><span class=\"line\">label.textColor <span class=\"operator\">=</span> <span class=\"type\">UIColor</span>.black</span><br><span class=\"line\">label.numberOfLines <span class=\"operator\">=</span> <span class=\"number\">0</span></span><br><span class=\"line\">label.lineBreakMode <span class=\"operator\">=</span> .byWordWrapping</span><br><span class=\"line\">label.text <span class=\"operator\">=</span> date</span><br><span class=\"line\">view.addSubview(label)</span><br></pre></td></tr></table></figure>\n\n<p>Take note of the <code>DateHelperKt.getDate</code> method call to Kotlin. At this point you should be able to run the iOS app and see the current date. </p>\n<h2 id=\"References\"><a href=\"#References\" class=\"headerlink\" title=\"References\"></a>References</h2><p>This is a basic example of setting up code sharing with Kotlin between iOS and Android, below are some references to documentation and libraries: </p>\n<ul>\n<li><a href=\"https://kotlinlang.org/docs/reference/native/objc_interop.html\">Kotlin/Native interoperability with Swift/Objective-C</a></li>\n<li><a href=\"https://kotlinlang.org/docs/reference/multiplatform.html\">Multiplatform Programming</a></li>\n<li><a href=\"https://github.com/Kotlin/kotlinx.coroutines/blob/master/README.md\">kotlinx.coroutines</a></li>\n<li><a href=\"https://github.com/Kotlin/kotlinx-io\">kotlinx-io</a></li>\n<li><a href=\"https://github.com/Kotlin/kotlinx.serialization\">kotlinx.serialization</a></li>\n<li><a href=\"https://ktor.io/\">Ktor</a></li>\n<li><a href=\"https://ktor.io/clients/index.html\">Ktor HTTP client</a></li>\n</ul>\n","site":{"data":{"projects":[{"name":"koop-provider-pg","url":"https://www.npmjs.com/package/koop-provider-pg","desc":"Koop provider to query PostGIS spatial data"},{"name":"Map Preview","url":"https://packagecontrol.io/packages/MapPreview","desc":"Preview map layer files in your web browser using Sublime Text 3"},{"name":"kotlin mulitplatform template","url":"https://github.com/doneill/kotlin-multiplatform-template","desc":"Kotlin MPP template to bootstrap Mobile/Web"}]}},"excerpt":"","more":"<p>Multiplatform projects is a feature in Kotlin that allows for sharing Kotlin code between iOS and Android. Android leverages the Kotlin/JVM and iOS uses Kotlin/Native which allows iOS apps to call Kotlin from Swift. This post will step us through getting an environment setup for Android and iOS development. A completed template project is available on <a href=\"https://github.com/doneill/kotlin-multiplatform-template\">GitHub</a>.</p>\n<h2 id=\"Update-06-28-2020\"><a href=\"#Update-06-28-2020\" class=\"headerlink\" title=\"Update 06/28/2020\"></a>Update 06/28/2020</h2><p>These steps should still work if followed in order but the original repo on <a href=\"https://github.com/doneill/kotlin-multiplatform-template\">GitHub</a> has changed beyond just setting up the dev environment and can still be used as a guide to accompany this post. </p>\n<h2 id=\"Local-Environment\"><a href=\"#Local-Environment\" class=\"headerlink\" title=\"Local Environment\"></a>Local Environment</h2><ul>\n<li><a href=\"https://developer.android.com/studio\">Android Studio</a></li>\n<li>Kotlin plugin 1.3.50 or higher should be installed in the IDE. </li>\n<li><a href=\"https://developer.apple.com/xcode/\">Xcode</a> </li>\n</ul>\n<h2 id=\"Create-an-Android-Project\"><a href=\"#Create-an-Android-Project\" class=\"headerlink\" title=\"Create an Android Project\"></a>Create an Android Project</h2><p>Create an empty activity project within Android Studio with <strong>File > New > New Project</strong> from the toolbar. Ensure that <strong>Kotlin</strong> is the selected language or ensure Kotlin support is checked (depending on version of Android Studio).</p>\n<p>Kotlin native requires a recent version of Gradle, ensure that the <strong>gradle/wrapper/gradle-wrapper.properties</strong> file is grabbing 5.5.1 or higher with the following <strong>distributionUrl</strong>: </p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">distributionUrl=https\\://services.gradle.org/distributions/gradle-5.5.1-all.zip</span><br></pre></td></tr></table></figure>\n\n<p>You should be able to compile and run your new Android app. </p>\n<h2 id=\"Create-an-Shared-Java-Library-module\"><a href=\"#Create-an-Shared-Java-Library-module\" class=\"headerlink\" title=\"Create an Shared Java Library module\"></a>Create an Shared Java Library module</h2><p>Create a new module with <strong>File > New > New Module</strong> from the toolbar, then select <strong>Java Library</strong>. We will assume the library name is <strong>lib</strong>, but you can name it anything you want. </p>\n<h3 id=\"Setup-folder-structure-for-shared-Java-lib\"><a href=\"#Setup-folder-structure-for-shared-Java-lib\" class=\"headerlink\" title=\"Setup folder structure for shared Java lib\"></a>Setup folder structure for shared Java lib</h3><p>Create the following subdirectories under your new <strong>lib</strong> module: </p>\n<ul>\n<li>androidMain</li>\n<li>commonMain</li>\n<li>iosMain</li>\n</ul>\n<p>With a corresponding <strong>kotlin</strong> folder underneath, resembling the structure below:</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br><span class=\"line\">8</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">-- lib</span><br><span class=\"line\"> -- src</span><br><span class=\"line\"> -- androidMain</span><br><span class=\"line\"> -- kotlin</span><br><span class=\"line\"> -- commonMain</span><br><span class=\"line\"> -- kotlin</span><br><span class=\"line\"> -- iosMain</span><br><span class=\"line\"> -- kotlin</span><br></pre></td></tr></table></figure>\n\n<h3 id=\"Update-shared-lib-build-script\"><a href=\"#Update-shared-lib-build-script\" class=\"headerlink\" title=\"Update shared lib build script\"></a>Update shared lib build script</h3><p>We will need to update the lib module build script to be a kotlin multiplatform lib. Rename the <strong>lib/build.gradle</strong> file to <strong>lib/build.gradle.kts</strong>, then remove all the contents, and replace with the following code: </p>\n<figure class=\"highlight kotlin\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br><span class=\"line\">8</span><br><span class=\"line\">9</span><br><span class=\"line\">10</span><br><span class=\"line\">11</span><br><span class=\"line\">12</span><br><span class=\"line\">13</span><br><span class=\"line\">14</span><br><span class=\"line\">15</span><br><span class=\"line\">16</span><br><span class=\"line\">17</span><br><span class=\"line\">18</span><br><span class=\"line\">19</span><br><span class=\"line\">20</span><br><span class=\"line\">21</span><br><span class=\"line\">22</span><br><span class=\"line\">23</span><br><span class=\"line\">24</span><br><span class=\"line\">25</span><br><span class=\"line\">26</span><br><span class=\"line\">27</span><br><span class=\"line\">28</span><br><span class=\"line\">29</span><br><span class=\"line\">30</span><br><span class=\"line\">31</span><br><span class=\"line\">32</span><br><span class=\"line\">33</span><br><span class=\"line\">34</span><br><span class=\"line\">35</span><br><span class=\"line\">36</span><br><span class=\"line\">37</span><br><span class=\"line\">38</span><br><span class=\"line\">39</span><br><span class=\"line\">40</span><br><span class=\"line\">41</span><br><span class=\"line\">42</span><br><span class=\"line\">43</span><br><span class=\"line\">44</span><br><span class=\"line\">45</span><br><span class=\"line\">46</span><br><span class=\"line\">47</span><br><span class=\"line\">48</span><br><span class=\"line\">49</span><br><span class=\"line\">50</span><br><span class=\"line\">51</span><br><span class=\"line\">52</span><br><span class=\"line\">53</span><br><span class=\"line\">54</span><br><span class=\"line\">55</span><br><span class=\"line\">56</span><br><span class=\"line\">57</span><br><span class=\"line\">58</span><br><span class=\"line\">59</span><br><span class=\"line\">60</span><br><span class=\"line\">61</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">import</span> org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget</span><br><span class=\"line\"></span><br><span class=\"line\">plugins {</span><br><span class=\"line\"> kotlin(<span class=\"string\">"multiplatform"</span>)</span><br><span class=\"line\">}</span><br><span class=\"line\"></span><br><span class=\"line\">kotlin {</span><br><span class=\"line\"> <span class=\"comment\">//select iOS target platform depending on the Xcode environment variables</span></span><br><span class=\"line\"> <span class=\"keyword\">val</span> iOSTarget: (String, KotlinNativeTarget.() -> <span class=\"built_in\">Unit</span>) -> KotlinNativeTarget =</span><br><span class=\"line\"> <span class=\"keyword\">if</span> (System.getenv(<span class=\"string\">"SDK_NAME"</span>)?.startsWith(<span class=\"string\">"iphoneos"</span>) == <span class=\"literal\">true</span>)</span><br><span class=\"line\"> ::iosArm64</span><br><span class=\"line\"> <span class=\"keyword\">else</span></span><br><span class=\"line\"> ::iosX64</span><br><span class=\"line\"></span><br><span class=\"line\"> iOSTarget(<span class=\"string\">"ios"</span>) {</span><br><span class=\"line\"> binaries {</span><br><span class=\"line\"> framework {</span><br><span class=\"line\"> baseName = <span class=\"string\">"SharedCode"</span></span><br><span class=\"line\"> }</span><br><span class=\"line\"> }</span><br><span class=\"line\"> }</span><br><span class=\"line\"></span><br><span class=\"line\"> jvm(<span class=\"string\">"android"</span>)</span><br><span class=\"line\"></span><br><span class=\"line\"> sourceSets[<span class=\"string\">"commonMain"</span>].dependencies {</span><br><span class=\"line\"> implementation(<span class=\"string\">"org.jetbrains.kotlin:kotlin-stdlib-common"</span>)</span><br><span class=\"line\"> }</span><br><span class=\"line\"></span><br><span class=\"line\"> sourceSets[<span class=\"string\">"androidMain"</span>].dependencies {</span><br><span class=\"line\"> implementation(<span class=\"string\">"org.jetbrains.kotlin:kotlin-stdlib"</span>)</span><br><span class=\"line\"> }</span><br><span class=\"line\">}</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"keyword\">val</span> packForXcode <span class=\"keyword\">by</span> tasks.creating(Sync::<span class=\"class\"><span class=\"keyword\">class</span>) </span>{</span><br><span class=\"line\"> <span class=\"keyword\">val</span> targetDir = File(buildDir, <span class=\"string\">"xcode-frameworks"</span>)</span><br><span class=\"line\"></span><br><span class=\"line\"> <span class=\"comment\">/// selecting the right configuration for the iOS</span></span><br><span class=\"line\"> <span class=\"comment\">/// framework depending on the environment</span></span><br><span class=\"line\"> <span class=\"comment\">/// variables set by Xcode build</span></span><br><span class=\"line\"> <span class=\"keyword\">val</span> mode = System.getenv(<span class=\"string\">"CONFIGURATION"</span>) ?: <span class=\"string\">"DEBUG"</span></span><br><span class=\"line\"> <span class=\"keyword\">val</span> framework = kotlin.targets</span><br><span class=\"line\"> .getByName<KotlinNativeTarget>(<span class=\"string\">"ios"</span>)</span><br><span class=\"line\"> .binaries.getFramework(mode)</span><br><span class=\"line\"> inputs.property(<span class=\"string\">"mode"</span>, mode)</span><br><span class=\"line\"> dependsOn(framework.linkTask)</span><br><span class=\"line\"></span><br><span class=\"line\"> from({ framework.outputDirectory })</span><br><span class=\"line\"> into(targetDir)</span><br><span class=\"line\"></span><br><span class=\"line\"> <span class=\"comment\">/// generate a helpful ./gradlew wrapper with embedded Java path</span></span><br><span class=\"line\"> doLast {</span><br><span class=\"line\"> <span class=\"keyword\">val</span> gradlew = File(targetDir, <span class=\"string\">"gradlew"</span>)</span><br><span class=\"line\"> gradlew.writeText(<span class=\"string\">"#!/bin/bash\\n"</span></span><br><span class=\"line\"> + <span class=\"string\">"export 'JAVA_HOME=<span class=\"subst\">${System.getProperty(<span class=\"string\">"java.home"</span>)}</span>'\\n"</span></span><br><span class=\"line\"> + <span class=\"string\">"cd '<span class=\"subst\">${rootProject.rootDir}</span>'\\n"</span></span><br><span class=\"line\"> + <span class=\"string\">"./gradlew \\$@\\n"</span>)</span><br><span class=\"line\"> gradlew.setExecutable(<span class=\"literal\">true</span>)</span><br><span class=\"line\"> }</span><br><span class=\"line\">}</span><br><span class=\"line\"></span><br><span class=\"line\">tasks.getByName(<span class=\"string\">"build"</span>).dependsOn(packForXcode)</span><br></pre></td></tr></table></figure>\n\n<p>The update build script uses <code>kotlin-multiplatform</code> plugin and defines several targets. It also defines a <code>packForXcode</code> task which configures a framework lib to link in our iOS app we will create in a later step. </p>\n<h2 id=\"Implement-shared-lib-module\"><a href=\"#Implement-shared-lib-module\" class=\"headerlink\" title=\"Implement shared lib module\"></a>Implement shared lib module</h2><p>Create a new package and kotlin source file under <strong>lib/src/commonMain/DateHelper.kt</strong>. Add the following code to get the current date as <code>String</code>. </p>\n<figure class=\"highlight kotlin\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">package</span> com.yourdomain.platform</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"keyword\">expect</span> <span class=\"function\"><span class=\"keyword\">fun</span> <span class=\"title\">getCurrentDate</span><span class=\"params\">()</span></span>: String</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"function\"><span class=\"keyword\">fun</span> <span class=\"title\">getDate</span><span class=\"params\">()</span></span>: String {</span><br><span class=\"line\"> <span class=\"keyword\">return</span> <span class=\"string\">"Today's Date is <span class=\"subst\">${getCurrentDate()}</span>"</span></span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n\n<p>The method dependent on platform implementation is marked as <code>expect</code>. The method called by Android and iOS app clients is <code>getDate()</code>. </p>\n<p>The common lib implementation is done in <strong>androidMain</strong> and <strong>iosMain</strong> respectively. Create the same package as <strong>commonMain</strong> with a new kotlin source file under <strong>lib/src/androidMain/DateHelperAnd.kt</strong> and <strong>lib/src/iosMain/DateHelperIos.kt</strong></p>\n<p>Add the following for <strong>DateHelperAnd.kt</strong></p>\n<figure class=\"highlight kotlin\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">package</span> com.yourdomain.platform</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"keyword\">import</span> java.util.Date</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"keyword\">actual</span> <span class=\"function\"><span class=\"keyword\">fun</span> <span class=\"title\">getCurrentDate</span><span class=\"params\">()</span></span>: String = Date().toString()</span><br></pre></td></tr></table></figure>\n\n<p>Add the following for <strong>DateHelperIos.kt</strong></p>\n<figure class=\"highlight kotlin\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">package</span> com.yourdomain.platform</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"keyword\">import</span> platform.Foundation.NSDate</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"keyword\">actual</span> <span class=\"function\"><span class=\"keyword\">fun</span> <span class=\"title\">getCurrentDate</span><span class=\"params\">()</span></span> = NSDate().toString()</span><br></pre></td></tr></table></figure>\n\n<p>The implementation of the <code>expect</code> methods are marked as <code>actual</code>. Kotlin/Native compiler comes with a set of pre-imported frameworks, so we can use the <code>NSDate</code>. Objective C and Swift interop covered in more detail <a href=\"https://kotlinlang.org/docs/reference/native/objc_interop.html\">here</a>.</p>\n<h2 id=\"Link-shared-module-in-Android\"><a href=\"#Link-shared-module-in-Android\" class=\"headerlink\" title=\"Link shared module in Android\"></a>Link shared module in Android</h2><p>In order for our Android app to depend on the shared lib we must link it as a dependency. Open the <strong>app/build.gradle</strong> file and add the following under <strong>dependencies</strong> block:</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"></span><br><span class=\"line\">dependencies {</span><br><span class=\"line\"> implementation project (':lib')</span><br><span class=\"line\"> ... </span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n\n<h2 id=\"Update-the-Android-app\"><a href=\"#Update-the-Android-app\" class=\"headerlink\" title=\"Update the Android app\"></a>Update the Android app</h2><p>Using the default <code>TextView</code> added when we generated an Android project, let’s assign an <code>id</code> to the <code>TextView</code> in the activity so we can access it from code. Open <strong>app/src/main/res/layout/activity_main.xml</strong> and add the <code>id</code>: </p>\n<figure class=\"highlight xml\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"tag\"><<span class=\"name\">TextView</span> <span class=\"attr\">android:id</span>=<span class=\"string\">"@+id/date_view"</span></span></span><br><span class=\"line\"><span class=\"tag\"> <span class=\"attr\">...</span></span></span><br><span class=\"line\"><span class=\"tag\"> /></span></span><br></pre></td></tr></table></figure>\n\n<p>Now we can add the following line of code to the <code>MainActivity</code> class in <strong>/app/src/main/java/[package]/MainActivity.kt</strong> file to the end of the <code>onCreate</code> method: </p>\n<figure class=\"highlight kotlin\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">findViewById<TextView>(R.id.date_view).text = getDate()</span><br></pre></td></tr></table></figure>\n\n<p>At this point you should be able to run the Android app and see the current date.</p>\n<h2 id=\"Create-iOS-project\"><a href=\"#Create-iOS-project\" class=\"headerlink\" title=\"Create iOS project\"></a>Create iOS project</h2><p>Fire up Xcode and create a new <strong>Single View App</strong> with <strong>File > New > Project</strong>. Create a project name, e.g. <strong>ios-app</strong>, and set the directory inside of the project directory created with Android Studio. Your resulting project structure should look similar to this: </p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">-- Project</span><br><span class=\"line\"> -- app</span><br><span class=\"line\"> -- lib</span><br><span class=\"line\"> -- iosApp</span><br></pre></td></tr></table></figure>\n\n\n<h2 id=\"Generate-shared-framework-and-link-to-project\"><a href=\"#Generate-shared-framework-and-link-to-project\" class=\"headerlink\" title=\"Generate shared framework and link to project\"></a>Generate shared framework and link to project</h2><p>We need to build the framework initially before we can link it to our ios project using the <code>build</code> task from our common lib module. Open a terminal and cd into the <strong>lib</strong> module folder and run the following: </p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">$ ./gradlew clean packForXcode --info</span><br></pre></td></tr></table></figure>\n\n<p>This will run the <code>packForXCode</code> task we defined and configure the framework inside of the <strong>lib/build/xcode-frameworks</strong> folder. </p>\n<h3 id=\"Add-embedded-libs\"><a href=\"#Add-embedded-libs\" class=\"headerlink\" title=\"Add embedded libs\"></a>Add embedded libs</h3><p>Select the <strong>ios-app</strong> project inside xcode file view to open the project properties. Under the <strong>General</strong> tab scroll down to <strong>Frameworks, Libraries, and Embedded Content</strong> and click on the <code>+</code> button to add the framework. Select the <strong>SharedContent.framework</strong> and click <strong>Add</strong>. </p>\n<h3 id=\"Update-build-settings\"><a href=\"#Update-build-settings\" class=\"headerlink\" title=\"Update build settings\"></a>Update build settings</h3><p>With the project properties open, select the <strong>Build Settings</strong> tab and search for <strong>EFramework Search Paths</strong> and update the path to <code>$(SRCROOT)/../lib/build/xcode-frameworks</code></p>\n<h3 id=\"Create-new-run-script\"><a href=\"#Create-new-run-script\" class=\"headerlink\" title=\"Create new run script\"></a>Create new run script</h3><p>To keep the framework updated with new builds, select the <strong>Build Rules</strong> tab and add a new run script phase. Add the following to the shell script: </p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">cd "$SRCROOT/../lib/build/xcode-frameworks"</span><br><span class=\"line\">./gradlew :lib:packForXCode -PXCODE_CONFIGURATION=${CONFIGURATION}</span><br></pre></td></tr></table></figure>\n\n<p>This will keep the framework up to date on successive builds. </p>\n<h2 id=\"Update-the-iOS-App\"><a href=\"#Update-the-iOS-App\" class=\"headerlink\" title=\"Update the iOS App\"></a>Update the iOS App</h2><p>The iOS app will use the common lib from Kotlin. Open up the <strong>ViewController.swift</strong> file and add the following import: </p>\n<figure class=\"highlight swift\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"comment\">// import the framework</span></span><br><span class=\"line\"><span class=\"keyword\">import</span> main</span><br></pre></td></tr></table></figure>\n\n<p>Now append the following to the <code>viewDidLoad()</code> method: </p>\n<figure class=\"highlight swift\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br><span class=\"line\">8</span><br><span class=\"line\">9</span><br><span class=\"line\">10</span><br><span class=\"line\">11</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">let</span> date <span class=\"operator\">=</span> <span class=\"type\">DateHelperKt</span>.getDate()</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"keyword\">let</span> label <span class=\"operator\">=</span> <span class=\"type\">UILabel</span>(frame: <span class=\"type\">CGRect</span>(x: <span class=\"number\">0</span>, y: <span class=\"number\">0</span>, width: <span class=\"number\">230</span>, height: <span class=\"number\">42</span>))</span><br><span class=\"line\">label.center <span class=\"operator\">=</span> <span class=\"type\">CGPoint</span>(x: <span class=\"number\">160</span>, y: <span class=\"number\">285</span>)</span><br><span class=\"line\">label.textAlignment <span class=\"operator\">=</span> .center</span><br><span class=\"line\">label.font <span class=\"operator\">=</span> label.font.withSize(<span class=\"number\">16</span>)</span><br><span class=\"line\">label.textColor <span class=\"operator\">=</span> <span class=\"type\">UIColor</span>.black</span><br><span class=\"line\">label.numberOfLines <span class=\"operator\">=</span> <span class=\"number\">0</span></span><br><span class=\"line\">label.lineBreakMode <span class=\"operator\">=</span> .byWordWrapping</span><br><span class=\"line\">label.text <span class=\"operator\">=</span> date</span><br><span class=\"line\">view.addSubview(label)</span><br></pre></td></tr></table></figure>\n\n<p>Take note of the <code>DateHelperKt.getDate</code> method call to Kotlin. At this point you should be able to run the iOS app and see the current date. </p>\n<h2 id=\"References\"><a href=\"#References\" class=\"headerlink\" title=\"References\"></a>References</h2><p>This is a basic example of setting up code sharing with Kotlin between iOS and Android, below are some references to documentation and libraries: </p>\n<ul>\n<li><a href=\"https://kotlinlang.org/docs/reference/native/objc_interop.html\">Kotlin/Native interoperability with Swift/Objective-C</a></li>\n<li><a href=\"https://kotlinlang.org/docs/reference/multiplatform.html\">Multiplatform Programming</a></li>\n<li><a href=\"https://github.com/Kotlin/kotlinx.coroutines/blob/master/README.md\">kotlinx.coroutines</a></li>\n<li><a href=\"https://github.com/Kotlin/kotlinx-io\">kotlinx-io</a></li>\n<li><a href=\"https://github.com/Kotlin/kotlinx.serialization\">kotlinx.serialization</a></li>\n<li><a href=\"https://ktor.io/\">Ktor</a></li>\n<li><a href=\"https://ktor.io/clients/index.html\">Ktor HTTP client</a></li>\n</ul>\n"},{"title":"Process geojson with jq","date":"2020-04-01T01:18:25.000Z","_content":"\nEsri Feature Services support geojson output, this url gets the county level covid-19 data\n\n```bash\n# get raw data\ncurl 'https://services1.arcgis.com/0MSEUqKaxRlEPj5g/ArcGIS/rest/services/ncov_cases_US/FeatureServer/0/query?where=1%3D1&outFields=*&f=geojson&token='\n```\n\nSample output\n\n```json\n{\"type\":\"FeatureCollection\",\"features\":[{\"type\":\"Feature\",\"id\":1,\"geometry\":{\"type\":\"Point\",\"coordinates\":[-82.4617065799999,34.2233337800001]},\"properties\":{\"OBJECTID\":1,\"Province_State\":\"South Carolina\",\"Country_Region\":\"US\",\"Last_Update\":1585698236000,\"Lat\":34.22333378,\"Long_\":-82.46170658,\"Confirmed\":4,\"Recovered\":0,\"Deaths\":0,\"Active\":0,\"Admin2\":\"Abbeville\",\"FIPS\":\"45001\",\"Combined_Key\":\"Abbeville, South Carolina, US\",\"Incident_Rate\":null,\"People_Tested\":null}},\n```\n\nThe raw geojson format isn't returned so nicely formatted so we can pipe the response through [jq](https://stedolan.github.io/jq/) to pretty print it:\n\n```bash\ncurl 'https://services1.arcgis.com/0MSEUqKaxRlEPj5g/ArcGIS/rest/services/ncov_cases_US/FeatureServer/0/query?where=1%3D1&outFields=*&f=geojson&token=' | jq '.'\n```\n\nWe can use jq to return all features:\n\n```bash\ncurl 'https://services1.arcgis.com/0MSEUqKaxRlEPj5g/ArcGIS/rest/services/ncov_cases_US/FeatureServer/0/query?where=1%3D1&outFields=*&f=geojson&token=' | jq '.features[]'\n```\n\nSample result\n\n```json\n{\n \"type\": \"Feature\",\n \"id\": 2173,\n \"geometry\": {\n \"type\": \"Point\",\n \"coordinates\": [\n -102.4258673,\n 40.0034683900001\n ]\n },\n \"properties\": {\n \"OBJECTID\": 2173,\n \"Province_State\": \"Colorado\",\n \"Country_Region\": \"US\",\n \"Last_Update\": 1585702369000,\n \"Lat\": 40.00346839,\n \"Long_\": -102.4258673,\n \"Confirmed\": 2,\n \"Recovered\": 0,\n \"Deaths\": 0,\n \"Active\": 0,\n \"Admin2\": \"Yuma\",\n \"FIPS\": \"08125\",\n \"Combined_Key\": \"Yuma, Colorado, US\",\n \"Incident_Rate\": null,\n \"People_Tested\": null\n }\n```\n\nThe function `select(boolean_expression)` will return features that are true for the expression. We can filter our data by State with `select(.properties.Province_State==\"Washington\")`: \n\n```bash\n# get Washington county data\ncurl 'https://services1.arcgis.com/0MSEUqKaxRlEPj5g/ArcGIS/rest/services/ncov_cases_US/FeatureServer/0/query?where=1%3D1&outFields=*&f=geojson&token=' | jq '.features[] | select(.properties.Province_State==\"Washington\")'\n```\n\nSample result: \n\n```json\n{\n \"type\": \"Feature\",\n \"id\": 2162,\n \"geometry\": {\n \"type\": \"Point\",\n \"coordinates\": [\n -120.7380126,\n 46.45738486\n ]\n },\n \"properties\": {\n \"OBJECTID\": 2162,\n \"Province_State\": \"Washington\",\n \"Country_Region\": \"US\",\n \"Last_Update\": 1585710693000,\n \"Lat\": 46.45738486,\n \"Long_\": -120.7380126,\n \"Confirmed\": 161,\n \"Recovered\": 0,\n \"Deaths\": 3,\n \"Active\": 0,\n \"Admin2\": \"Yakima\",\n \"FIPS\": \"53077\",\n \"Combined_Key\": \"Yakima, Washington, US\",\n \"Incident_Rate\": null,\n \"People_Tested\": null\n }\n}\n```\n\nThen you can pipe the results to a local geojson file\n\n```bash\n# pipe result to geojson file\ncurl 'https://services1.arcgis.com/0MSEUqKaxRlEPj5g/ArcGIS/rest/services/ncov_cases_US/FeatureServer/0/query?where=1%3D1&outFields=*&f=geojson&token=' | jq '.features[] | select(.properties.Province_State==\"Washington\")' > counties-wa-point.geojson\n```\n","source":"_posts/jq-geojson.md","raw":"---\ntitle: Process geojson with jq\ndate: 2020-03-31 18:18:25\ntags:\n- esri\n- arcgis rest\n- geojson\n---\n\nEsri Feature Services support geojson output, this url gets the county level covid-19 data\n\n```bash\n# get raw data\ncurl 'https://services1.arcgis.com/0MSEUqKaxRlEPj5g/ArcGIS/rest/services/ncov_cases_US/FeatureServer/0/query?where=1%3D1&outFields=*&f=geojson&token='\n```\n\nSample output\n\n```json\n{\"type\":\"FeatureCollection\",\"features\":[{\"type\":\"Feature\",\"id\":1,\"geometry\":{\"type\":\"Point\",\"coordinates\":[-82.4617065799999,34.2233337800001]},\"properties\":{\"OBJECTID\":1,\"Province_State\":\"South Carolina\",\"Country_Region\":\"US\",\"Last_Update\":1585698236000,\"Lat\":34.22333378,\"Long_\":-82.46170658,\"Confirmed\":4,\"Recovered\":0,\"Deaths\":0,\"Active\":0,\"Admin2\":\"Abbeville\",\"FIPS\":\"45001\",\"Combined_Key\":\"Abbeville, South Carolina, US\",\"Incident_Rate\":null,\"People_Tested\":null}},\n```\n\nThe raw geojson format isn't returned so nicely formatted so we can pipe the response through [jq](https://stedolan.github.io/jq/) to pretty print it:\n\n```bash\ncurl 'https://services1.arcgis.com/0MSEUqKaxRlEPj5g/ArcGIS/rest/services/ncov_cases_US/FeatureServer/0/query?where=1%3D1&outFields=*&f=geojson&token=' | jq '.'\n```\n\nWe can use jq to return all features:\n\n```bash\ncurl 'https://services1.arcgis.com/0MSEUqKaxRlEPj5g/ArcGIS/rest/services/ncov_cases_US/FeatureServer/0/query?where=1%3D1&outFields=*&f=geojson&token=' | jq '.features[]'\n```\n\nSample result\n\n```json\n{\n \"type\": \"Feature\",\n \"id\": 2173,\n \"geometry\": {\n \"type\": \"Point\",\n \"coordinates\": [\n -102.4258673,\n 40.0034683900001\n ]\n },\n \"properties\": {\n \"OBJECTID\": 2173,\n \"Province_State\": \"Colorado\",\n \"Country_Region\": \"US\",\n \"Last_Update\": 1585702369000,\n \"Lat\": 40.00346839,\n \"Long_\": -102.4258673,\n \"Confirmed\": 2,\n \"Recovered\": 0,\n \"Deaths\": 0,\n \"Active\": 0,\n \"Admin2\": \"Yuma\",\n \"FIPS\": \"08125\",\n \"Combined_Key\": \"Yuma, Colorado, US\",\n \"Incident_Rate\": null,\n \"People_Tested\": null\n }\n```\n\nThe function `select(boolean_expression)` will return features that are true for the expression. We can filter our data by State with `select(.properties.Province_State==\"Washington\")`: \n\n```bash\n# get Washington county data\ncurl 'https://services1.arcgis.com/0MSEUqKaxRlEPj5g/ArcGIS/rest/services/ncov_cases_US/FeatureServer/0/query?where=1%3D1&outFields=*&f=geojson&token=' | jq '.features[] | select(.properties.Province_State==\"Washington\")'\n```\n\nSample result: \n\n```json\n{\n \"type\": \"Feature\",\n \"id\": 2162,\n \"geometry\": {\n \"type\": \"Point\",\n \"coordinates\": [\n -120.7380126,\n 46.45738486\n ]\n },\n \"properties\": {\n \"OBJECTID\": 2162,\n \"Province_State\": \"Washington\",\n \"Country_Region\": \"US\",\n \"Last_Update\": 1585710693000,\n \"Lat\": 46.45738486,\n \"Long_\": -120.7380126,\n \"Confirmed\": 161,\n \"Recovered\": 0,\n \"Deaths\": 3,\n \"Active\": 0,\n \"Admin2\": \"Yakima\",\n \"FIPS\": \"53077\",\n \"Combined_Key\": \"Yakima, Washington, US\",\n \"Incident_Rate\": null,\n \"People_Tested\": null\n }\n}\n```\n\nThen you can pipe the results to a local geojson file\n\n```bash\n# pipe result to geojson file\ncurl 'https://services1.arcgis.com/0MSEUqKaxRlEPj5g/ArcGIS/rest/services/ncov_cases_US/FeatureServer/0/query?where=1%3D1&outFields=*&f=geojson&token=' | jq '.features[] | select(.properties.Province_State==\"Washington\")' > counties-wa-point.geojson\n```\n","slug":"jq-geojson","published":1,"updated":"2020-04-01T01:18:25.000Z","comments":1,"layout":"post","photos":[],"link":"","_id":"ckz0jkmcz000dnvhwbp6s8hoj","content":"<p>Esri Feature Services support geojson output, this url gets the county level covid-19 data</p>\n<figure class=\"highlight bash\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"comment\"># get raw data</span></span><br><span class=\"line\">curl <span class=\"string\">'https://services1.arcgis.com/0MSEUqKaxRlEPj5g/ArcGIS/rest/services/ncov_cases_US/FeatureServer/0/query?where=1%3D1&outFields=*&f=geojson&token='</span></span><br></pre></td></tr></table></figure>\n\n<p>Sample output</p>\n<figure class=\"highlight json\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">{<span class=\"attr\">"type"</span>:<span class=\"string\">"FeatureCollection"</span>,<span class=\"attr\">"features"</span>:[{<span class=\"attr\">"type"</span>:<span class=\"string\">"Feature"</span>,<span class=\"attr\">"id"</span>:<span class=\"number\">1</span>,<span class=\"attr\">"geometry"</span>:{<span class=\"attr\">"type"</span>:<span class=\"string\">"Point"</span>,<span class=\"attr\">"coordinates"</span>:[<span class=\"number\">-82.4617065799999</span>,<span class=\"number\">34.2233337800001</span>]},<span class=\"attr\">"properties"</span>:{<span class=\"attr\">"OBJECTID"</span>:<span class=\"number\">1</span>,<span class=\"attr\">"Province_State"</span>:<span class=\"string\">"South Carolina"</span>,<span class=\"attr\">"Country_Region"</span>:<span class=\"string\">"US"</span>,<span class=\"attr\">"Last_Update"</span>:<span class=\"number\">1585698236000</span>,<span class=\"attr\">"Lat"</span>:<span class=\"number\">34.22333378</span>,<span class=\"attr\">"Long_"</span>:<span class=\"number\">-82.46170658</span>,<span class=\"attr\">"Confirmed"</span>:<span class=\"number\">4</span>,<span class=\"attr\">"Recovered"</span>:<span class=\"number\">0</span>,<span class=\"attr\">"Deaths"</span>:<span class=\"number\">0</span>,<span class=\"attr\">"Active"</span>:<span class=\"number\">0</span>,<span class=\"attr\">"Admin2"</span>:<span class=\"string\">"Abbeville"</span>,<span class=\"attr\">"FIPS"</span>:<span class=\"string\">"45001"</span>,<span class=\"attr\">"Combined_Key"</span>:<span class=\"string\">"Abbeville, South Carolina, US"</span>,<span class=\"attr\">"Incident_Rate"</span>:<span class=\"literal\">null</span>,<span class=\"attr\">"People_Tested"</span>:<span class=\"literal\">null</span>}},</span><br></pre></td></tr></table></figure>\n\n<p>The raw geojson format isn’t returned so nicely formatted so we can pipe the response through <a href=\"https://stedolan.github.io/jq/\">jq</a> to pretty print it:</p>\n<figure class=\"highlight bash\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">curl <span class=\"string\">'https://services1.arcgis.com/0MSEUqKaxRlEPj5g/ArcGIS/rest/services/ncov_cases_US/FeatureServer/0/query?where=1%3D1&outFields=*&f=geojson&token='</span> | jq <span class=\"string\">'.'</span></span><br></pre></td></tr></table></figure>\n\n<p>We can use jq to return all features:</p>\n<figure class=\"highlight bash\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">curl <span class=\"string\">'https://services1.arcgis.com/0MSEUqKaxRlEPj5g/ArcGIS/rest/services/ncov_cases_US/FeatureServer/0/query?where=1%3D1&outFields=*&f=geojson&token='</span> | jq <span class=\"string\">'.features[]'</span></span><br></pre></td></tr></table></figure>\n\n<p>Sample result</p>\n<figure class=\"highlight json\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br><span class=\"line\">8</span><br><span class=\"line\">9</span><br><span class=\"line\">10</span><br><span class=\"line\">11</span><br><span class=\"line\">12</span><br><span class=\"line\">13</span><br><span class=\"line\">14</span><br><span class=\"line\">15</span><br><span class=\"line\">16</span><br><span class=\"line\">17</span><br><span class=\"line\">18</span><br><span class=\"line\">19</span><br><span class=\"line\">20</span><br><span class=\"line\">21</span><br><span class=\"line\">22</span><br><span class=\"line\">23</span><br><span class=\"line\">24</span><br><span class=\"line\">25</span><br><span class=\"line\">26</span><br><span class=\"line\">27</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">{</span><br><span class=\"line\"> <span class=\"attr\">"type"</span>: <span class=\"string\">"Feature"</span>,</span><br><span class=\"line\"> <span class=\"attr\">"id"</span>: <span class=\"number\">2173</span>,</span><br><span class=\"line\"> <span class=\"attr\">"geometry"</span>: {</span><br><span class=\"line\"> <span class=\"attr\">"type"</span>: <span class=\"string\">"Point"</span>,</span><br><span class=\"line\"> <span class=\"attr\">"coordinates"</span>: [</span><br><span class=\"line\"> <span class=\"number\">-102.4258673</span>,</span><br><span class=\"line\"> <span class=\"number\">40.0034683900001</span></span><br><span class=\"line\"> ]</span><br><span class=\"line\"> },</span><br><span class=\"line\"> <span class=\"attr\">"properties"</span>: {</span><br><span class=\"line\"> <span class=\"attr\">"OBJECTID"</span>: <span class=\"number\">2173</span>,</span><br><span class=\"line\"> <span class=\"attr\">"Province_State"</span>: <span class=\"string\">"Colorado"</span>,</span><br><span class=\"line\"> <span class=\"attr\">"Country_Region"</span>: <span class=\"string\">"US"</span>,</span><br><span class=\"line\"> <span class=\"attr\">"Last_Update"</span>: <span class=\"number\">1585702369000</span>,</span><br><span class=\"line\"> <span class=\"attr\">"Lat"</span>: <span class=\"number\">40.00346839</span>,</span><br><span class=\"line\"> <span class=\"attr\">"Long_"</span>: <span class=\"number\">-102.4258673</span>,</span><br><span class=\"line\"> <span class=\"attr\">"Confirmed"</span>: <span class=\"number\">2</span>,</span><br><span class=\"line\"> <span class=\"attr\">"Recovered"</span>: <span class=\"number\">0</span>,</span><br><span class=\"line\"> <span class=\"attr\">"Deaths"</span>: <span class=\"number\">0</span>,</span><br><span class=\"line\"> <span class=\"attr\">"Active"</span>: <span class=\"number\">0</span>,</span><br><span class=\"line\"> <span class=\"attr\">"Admin2"</span>: <span class=\"string\">"Yuma"</span>,</span><br><span class=\"line\"> <span class=\"attr\">"FIPS"</span>: <span class=\"string\">"08125"</span>,</span><br><span class=\"line\"> <span class=\"attr\">"Combined_Key"</span>: <span class=\"string\">"Yuma, Colorado, US"</span>,</span><br><span class=\"line\"> <span class=\"attr\">"Incident_Rate"</span>: <span class=\"literal\">null</span>,</span><br><span class=\"line\"> <span class=\"attr\">"People_Tested"</span>: <span class=\"literal\">null</span></span><br><span class=\"line\"> }</span><br></pre></td></tr></table></figure>\n\n<p>The function <code>select(boolean_expression)</code> will return features that are true for the expression. We can filter our data by State with <code>select(.properties.Province_State=="Washington")</code>: </p>\n<figure class=\"highlight bash\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"comment\"># get Washington county data</span></span><br><span class=\"line\">curl <span class=\"string\">'https://services1.arcgis.com/0MSEUqKaxRlEPj5g/ArcGIS/rest/services/ncov_cases_US/FeatureServer/0/query?where=1%3D1&outFields=*&f=geojson&token='</span> | jq <span class=\"string\">'.features[] | select(.properties.Province_State=="Washington")'</span></span><br></pre></td></tr></table></figure>\n\n<p>Sample result: </p>\n<figure class=\"highlight json\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br><span class=\"line\">8</span><br><span class=\"line\">9</span><br><span class=\"line\">10</span><br><span class=\"line\">11</span><br><span class=\"line\">12</span><br><span class=\"line\">13</span><br><span class=\"line\">14</span><br><span class=\"line\">15</span><br><span class=\"line\">16</span><br><span class=\"line\">17</span><br><span class=\"line\">18</span><br><span class=\"line\">19</span><br><span class=\"line\">20</span><br><span class=\"line\">21</span><br><span class=\"line\">22</span><br><span class=\"line\">23</span><br><span class=\"line\">24</span><br><span class=\"line\">25</span><br><span class=\"line\">26</span><br><span class=\"line\">27</span><br><span class=\"line\">28</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">{</span><br><span class=\"line\"> <span class=\"attr\">"type"</span>: <span class=\"string\">"Feature"</span>,</span><br><span class=\"line\"> <span class=\"attr\">"id"</span>: <span class=\"number\">2162</span>,</span><br><span class=\"line\"> <span class=\"attr\">"geometry"</span>: {</span><br><span class=\"line\"> <span class=\"attr\">"type"</span>: <span class=\"string\">"Point"</span>,</span><br><span class=\"line\"> <span class=\"attr\">"coordinates"</span>: [</span><br><span class=\"line\"> <span class=\"number\">-120.7380126</span>,</span><br><span class=\"line\"> <span class=\"number\">46.45738486</span></span><br><span class=\"line\"> ]</span><br><span class=\"line\"> },</span><br><span class=\"line\"> <span class=\"attr\">"properties"</span>: {</span><br><span class=\"line\"> <span class=\"attr\">"OBJECTID"</span>: <span class=\"number\">2162</span>,</span><br><span class=\"line\"> <span class=\"attr\">"Province_State"</span>: <span class=\"string\">"Washington"</span>,</span><br><span class=\"line\"> <span class=\"attr\">"Country_Region"</span>: <span class=\"string\">"US"</span>,</span><br><span class=\"line\"> <span class=\"attr\">"Last_Update"</span>: <span class=\"number\">1585710693000</span>,</span><br><span class=\"line\"> <span class=\"attr\">"Lat"</span>: <span class=\"number\">46.45738486</span>,</span><br><span class=\"line\"> <span class=\"attr\">"Long_"</span>: <span class=\"number\">-120.7380126</span>,</span><br><span class=\"line\"> <span class=\"attr\">"Confirmed"</span>: <span class=\"number\">161</span>,</span><br><span class=\"line\"> <span class=\"attr\">"Recovered"</span>: <span class=\"number\">0</span>,</span><br><span class=\"line\"> <span class=\"attr\">"Deaths"</span>: <span class=\"number\">3</span>,</span><br><span class=\"line\"> <span class=\"attr\">"Active"</span>: <span class=\"number\">0</span>,</span><br><span class=\"line\"> <span class=\"attr\">"Admin2"</span>: <span class=\"string\">"Yakima"</span>,</span><br><span class=\"line\"> <span class=\"attr\">"FIPS"</span>: <span class=\"string\">"53077"</span>,</span><br><span class=\"line\"> <span class=\"attr\">"Combined_Key"</span>: <span class=\"string\">"Yakima, Washington, US"</span>,</span><br><span class=\"line\"> <span class=\"attr\">"Incident_Rate"</span>: <span class=\"literal\">null</span>,</span><br><span class=\"line\"> <span class=\"attr\">"People_Tested"</span>: <span class=\"literal\">null</span></span><br><span class=\"line\"> }</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n\n<p>Then you can pipe the results to a local geojson file</p>\n<figure class=\"highlight bash\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"comment\"># pipe result to geojson file</span></span><br><span class=\"line\">curl <span class=\"string\">'https://services1.arcgis.com/0MSEUqKaxRlEPj5g/ArcGIS/rest/services/ncov_cases_US/FeatureServer/0/query?where=1%3D1&outFields=*&f=geojson&token='</span> | jq <span class=\"string\">'.features[] | select(.properties.Province_State=="Washington")'</span> > counties-wa-point.geojson</span><br></pre></td></tr></table></figure>\n","site":{"data":{"projects":[{"name":"koop-provider-pg","url":"https://www.npmjs.com/package/koop-provider-pg","desc":"Koop provider to query PostGIS spatial data"},{"name":"Map Preview","url":"https://packagecontrol.io/packages/MapPreview","desc":"Preview map layer files in your web browser using Sublime Text 3"},{"name":"kotlin mulitplatform template","url":"https://github.com/doneill/kotlin-multiplatform-template","desc":"Kotlin MPP template to bootstrap Mobile/Web"}]}},"excerpt":"","more":"<p>Esri Feature Services support geojson output, this url gets the county level covid-19 data</p>\n<figure class=\"highlight bash\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"comment\"># get raw data</span></span><br><span class=\"line\">curl <span class=\"string\">'https://services1.arcgis.com/0MSEUqKaxRlEPj5g/ArcGIS/rest/services/ncov_cases_US/FeatureServer/0/query?where=1%3D1&outFields=*&f=geojson&token='</span></span><br></pre></td></tr></table></figure>\n\n<p>Sample output</p>\n<figure class=\"highlight json\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">{<span class=\"attr\">"type"</span>:<span class=\"string\">"FeatureCollection"</span>,<span class=\"attr\">"features"</span>:[{<span class=\"attr\">"type"</span>:<span class=\"string\">"Feature"</span>,<span class=\"attr\">"id"</span>:<span class=\"number\">1</span>,<span class=\"attr\">"geometry"</span>:{<span class=\"attr\">"type"</span>:<span class=\"string\">"Point"</span>,<span class=\"attr\">"coordinates"</span>:[<span class=\"number\">-82.4617065799999</span>,<span class=\"number\">34.2233337800001</span>]},<span class=\"attr\">"properties"</span>:{<span class=\"attr\">"OBJECTID"</span>:<span class=\"number\">1</span>,<span class=\"attr\">"Province_State"</span>:<span class=\"string\">"South Carolina"</span>,<span class=\"attr\">"Country_Region"</span>:<span class=\"string\">"US"</span>,<span class=\"attr\">"Last_Update"</span>:<span class=\"number\">1585698236000</span>,<span class=\"attr\">"Lat"</span>:<span class=\"number\">34.22333378</span>,<span class=\"attr\">"Long_"</span>:<span class=\"number\">-82.46170658</span>,<span class=\"attr\">"Confirmed"</span>:<span class=\"number\">4</span>,<span class=\"attr\">"Recovered"</span>:<span class=\"number\">0</span>,<span class=\"attr\">"Deaths"</span>:<span class=\"number\">0</span>,<span class=\"attr\">"Active"</span>:<span class=\"number\">0</span>,<span class=\"attr\">"Admin2"</span>:<span class=\"string\">"Abbeville"</span>,<span class=\"attr\">"FIPS"</span>:<span class=\"string\">"45001"</span>,<span class=\"attr\">"Combined_Key"</span>:<span class=\"string\">"Abbeville, South Carolina, US"</span>,<span class=\"attr\">"Incident_Rate"</span>:<span class=\"literal\">null</span>,<span class=\"attr\">"People_Tested"</span>:<span class=\"literal\">null</span>}},</span><br></pre></td></tr></table></figure>\n\n<p>The raw geojson format isn’t returned so nicely formatted so we can pipe the response through <a href=\"https://stedolan.github.io/jq/\">jq</a> to pretty print it:</p>\n<figure class=\"highlight bash\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">curl <span class=\"string\">'https://services1.arcgis.com/0MSEUqKaxRlEPj5g/ArcGIS/rest/services/ncov_cases_US/FeatureServer/0/query?where=1%3D1&outFields=*&f=geojson&token='</span> | jq <span class=\"string\">'.'</span></span><br></pre></td></tr></table></figure>\n\n<p>We can use jq to return all features:</p>\n<figure class=\"highlight bash\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">curl <span class=\"string\">'https://services1.arcgis.com/0MSEUqKaxRlEPj5g/ArcGIS/rest/services/ncov_cases_US/FeatureServer/0/query?where=1%3D1&outFields=*&f=geojson&token='</span> | jq <span class=\"string\">'.features[]'</span></span><br></pre></td></tr></table></figure>\n\n<p>Sample result</p>\n<figure class=\"highlight json\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br><span class=\"line\">8</span><br><span class=\"line\">9</span><br><span class=\"line\">10</span><br><span class=\"line\">11</span><br><span class=\"line\">12</span><br><span class=\"line\">13</span><br><span class=\"line\">14</span><br><span class=\"line\">15</span><br><span class=\"line\">16</span><br><span class=\"line\">17</span><br><span class=\"line\">18</span><br><span class=\"line\">19</span><br><span class=\"line\">20</span><br><span class=\"line\">21</span><br><span class=\"line\">22</span><br><span class=\"line\">23</span><br><span class=\"line\">24</span><br><span class=\"line\">25</span><br><span class=\"line\">26</span><br><span class=\"line\">27</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">{</span><br><span class=\"line\"> <span class=\"attr\">"type"</span>: <span class=\"string\">"Feature"</span>,</span><br><span class=\"line\"> <span class=\"attr\">"id"</span>: <span class=\"number\">2173</span>,</span><br><span class=\"line\"> <span class=\"attr\">"geometry"</span>: {</span><br><span class=\"line\"> <span class=\"attr\">"type"</span>: <span class=\"string\">"Point"</span>,</span><br><span class=\"line\"> <span class=\"attr\">"coordinates"</span>: [</span><br><span class=\"line\"> <span class=\"number\">-102.4258673</span>,</span><br><span class=\"line\"> <span class=\"number\">40.0034683900001</span></span><br><span class=\"line\"> ]</span><br><span class=\"line\"> },</span><br><span class=\"line\"> <span class=\"attr\">"properties"</span>: {</span><br><span class=\"line\"> <span class=\"attr\">"OBJECTID"</span>: <span class=\"number\">2173</span>,</span><br><span class=\"line\"> <span class=\"attr\">"Province_State"</span>: <span class=\"string\">"Colorado"</span>,</span><br><span class=\"line\"> <span class=\"attr\">"Country_Region"</span>: <span class=\"string\">"US"</span>,</span><br><span class=\"line\"> <span class=\"attr\">"Last_Update"</span>: <span class=\"number\">1585702369000</span>,</span><br><span class=\"line\"> <span class=\"attr\">"Lat"</span>: <span class=\"number\">40.00346839</span>,</span><br><span class=\"line\"> <span class=\"attr\">"Long_"</span>: <span class=\"number\">-102.4258673</span>,</span><br><span class=\"line\"> <span class=\"attr\">"Confirmed"</span>: <span class=\"number\">2</span>,</span><br><span class=\"line\"> <span class=\"attr\">"Recovered"</span>: <span class=\"number\">0</span>,</span><br><span class=\"line\"> <span class=\"attr\">"Deaths"</span>: <span class=\"number\">0</span>,</span><br><span class=\"line\"> <span class=\"attr\">"Active"</span>: <span class=\"number\">0</span>,</span><br><span class=\"line\"> <span class=\"attr\">"Admin2"</span>: <span class=\"string\">"Yuma"</span>,</span><br><span class=\"line\"> <span class=\"attr\">"FIPS"</span>: <span class=\"string\">"08125"</span>,</span><br><span class=\"line\"> <span class=\"attr\">"Combined_Key"</span>: <span class=\"string\">"Yuma, Colorado, US"</span>,</span><br><span class=\"line\"> <span class=\"attr\">"Incident_Rate"</span>: <span class=\"literal\">null</span>,</span><br><span class=\"line\"> <span class=\"attr\">"People_Tested"</span>: <span class=\"literal\">null</span></span><br><span class=\"line\"> }</span><br></pre></td></tr></table></figure>\n\n<p>The function <code>select(boolean_expression)</code> will return features that are true for the expression. We can filter our data by State with <code>select(.properties.Province_State=="Washington")</code>: </p>\n<figure class=\"highlight bash\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"comment\"># get Washington county data</span></span><br><span class=\"line\">curl <span class=\"string\">'https://services1.arcgis.com/0MSEUqKaxRlEPj5g/ArcGIS/rest/services/ncov_cases_US/FeatureServer/0/query?where=1%3D1&outFields=*&f=geojson&token='</span> | jq <span class=\"string\">'.features[] | select(.properties.Province_State=="Washington")'</span></span><br></pre></td></tr></table></figure>\n\n<p>Sample result: </p>\n<figure class=\"highlight json\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br><span class=\"line\">8</span><br><span class=\"line\">9</span><br><span class=\"line\">10</span><br><span class=\"line\">11</span><br><span class=\"line\">12</span><br><span class=\"line\">13</span><br><span class=\"line\">14</span><br><span class=\"line\">15</span><br><span class=\"line\">16</span><br><span class=\"line\">17</span><br><span class=\"line\">18</span><br><span class=\"line\">19</span><br><span class=\"line\">20</span><br><span class=\"line\">21</span><br><span class=\"line\">22</span><br><span class=\"line\">23</span><br><span class=\"line\">24</span><br><span class=\"line\">25</span><br><span class=\"line\">26</span><br><span class=\"line\">27</span><br><span class=\"line\">28</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">{</span><br><span class=\"line\"> <span class=\"attr\">"type"</span>: <span class=\"string\">"Feature"</span>,</span><br><span class=\"line\"> <span class=\"attr\">"id"</span>: <span class=\"number\">2162</span>,</span><br><span class=\"line\"> <span class=\"attr\">"geometry"</span>: {</span><br><span class=\"line\"> <span class=\"attr\">"type"</span>: <span class=\"string\">"Point"</span>,</span><br><span class=\"line\"> <span class=\"attr\">"coordinates"</span>: [</span><br><span class=\"line\"> <span class=\"number\">-120.7380126</span>,</span><br><span class=\"line\"> <span class=\"number\">46.45738486</span></span><br><span class=\"line\"> ]</span><br><span class=\"line\"> },</span><br><span class=\"line\"> <span class=\"attr\">"properties"</span>: {</span><br><span class=\"line\"> <span class=\"attr\">"OBJECTID"</span>: <span class=\"number\">2162</span>,</span><br><span class=\"line\"> <span class=\"attr\">"Province_State"</span>: <span class=\"string\">"Washington"</span>,</span><br><span class=\"line\"> <span class=\"attr\">"Country_Region"</span>: <span class=\"string\">"US"</span>,</span><br><span class=\"line\"> <span class=\"attr\">"Last_Update"</span>: <span class=\"number\">1585710693000</span>,</span><br><span class=\"line\"> <span class=\"attr\">"Lat"</span>: <span class=\"number\">46.45738486</span>,</span><br><span class=\"line\"> <span class=\"attr\">"Long_"</span>: <span class=\"number\">-120.7380126</span>,</span><br><span class=\"line\"> <span class=\"attr\">"Confirmed"</span>: <span class=\"number\">161</span>,</span><br><span class=\"line\"> <span class=\"attr\">"Recovered"</span>: <span class=\"number\">0</span>,</span><br><span class=\"line\"> <span class=\"attr\">"Deaths"</span>: <span class=\"number\">3</span>,</span><br><span class=\"line\"> <span class=\"attr\">"Active"</span>: <span class=\"number\">0</span>,</span><br><span class=\"line\"> <span class=\"attr\">"Admin2"</span>: <span class=\"string\">"Yakima"</span>,</span><br><span class=\"line\"> <span class=\"attr\">"FIPS"</span>: <span class=\"string\">"53077"</span>,</span><br><span class=\"line\"> <span class=\"attr\">"Combined_Key"</span>: <span class=\"string\">"Yakima, Washington, US"</span>,</span><br><span class=\"line\"> <span class=\"attr\">"Incident_Rate"</span>: <span class=\"literal\">null</span>,</span><br><span class=\"line\"> <span class=\"attr\">"People_Tested"</span>: <span class=\"literal\">null</span></span><br><span class=\"line\"> }</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n\n<p>Then you can pipe the results to a local geojson file</p>\n<figure class=\"highlight bash\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"comment\"># pipe result to geojson file</span></span><br><span class=\"line\">curl <span class=\"string\">'https://services1.arcgis.com/0MSEUqKaxRlEPj5g/ArcGIS/rest/services/ncov_cases_US/FeatureServer/0/query?where=1%3D1&outFields=*&f=geojson&token='</span> | jq <span class=\"string\">'.features[] | select(.properties.Province_State=="Washington")'</span> > counties-wa-point.geojson</span><br></pre></td></tr></table></figure>\n"},{"title":"Spatial References in Mobile APIs","date":"2019-05-25T03:46:25.000Z","_content":"\n\nModern Web mapping platforms have adopted [WGS 84 / Psuedo Mercator -- Spherical Mercator](https://en.wikipedia.org/wiki/Web_Mercator_projection) projected coordinate system, or more commonly known as Web Mercator. When working with most web mapping platforms points are in WGS84 and the APIs do the converting behind the scenes when showing the points in an app. Esri's ArcGIS Runtime platform does things a little differently, taking the Android API as an example, the spatial reference of an `ArcGISMap` is based on the first layer added to the map, all subsequent layers are automatically reprojected to the initial layers spatial reference when needed. \n\nArcGIS online basemaps are in Web Mercator, like other web mapping platforms, and when added as the first layer in an ArcGIS Runtime application it sets Web Mercator as the spatial reference of the map. When getting a location represented as a point from the API you will get the coordinates in Web Mercator. This is different from other mapping APIs. Taking Google Maps as an example whereby the underlying basemap you see in apps is in Web Mercator when you get a location represented as a point it will return WGS84 coordinates to work with the API. While in Google Maps the API handles the reprojection for you, the ArcGIS Runtime API requires the developer to handle to reprojection. \n\nThe ArcGIS Android API provides a `GeometryUtil` class that has a bunch of static methods to allow you to manipulate geometries. One such method for dealing with projections is `GeometryUtil.project()`. This class becomes quite useful when integrating between APIs that use spatial coordinates. \n\nThe ArcGIS Android `MapView` class can give us the center of the area visible to the user as a point in the spatial reference of the map. To work with other spatial APIs you can use the `GeometryUtil.project()` method to convert the point to WGS85.\n\n```kotlin\n// get the center point of the map\nval centerPnt = mapView.visibleArea.extent.center\nval centerPntWGS84 = GeometryEngine.project(centerPnt, SpatialReferences.getWgs84())\n\n// convert lat/lon as String\nval lat = centerPntWGS84.x.toString()\nval lon = centerPntWGS84.y.toString()\n```\n\nWe can use the resulting String representation of the WGS84 latitude and longitude to send to Google's place search API to get a list of predictions based on search input and the center of the map representing the location of where to set a radius around which to search:\n\n```kotlin\n@GET(\"api/place/autocomplete/json\")\nfun getPredictions(@Query(\"key\") apikey: String, \n @Query(\"input\") input: String, \n @Query(\"location\") location: String, \n @Query(\"radius\") radius: String): Call<Prediction>\n```\n\nThe results will be centered around the location you send it instead of null island. \n\nYou can also easily convert screen points to map coordinates in both Google and ArcGIS Android APIs, but they are accessed differently. The Google Maps API for Android provides a `Projection.toScreenLocation()` method to translate between screen location and geographic coordinates, in the ArcGIS Android API this can be done with `MapView.screenToLocation()`. These methods honor the returned point in the projection workflow described above. \n\nCheck out the weather-mpp on [GitHub](https://github.com/doneill/weather-map) for a working example of using Google Places API to zoom to a location result from a places search and map it on an ArcGIS Android Basemap. \n","source":"_posts/spatial-references.md","raw":"---\ntitle: Spatial References in Mobile APIs\ndate: 2019-05-24 20:46:25\ntags:\n- arcgis runtime\n- google maps\n---\n\n\nModern Web mapping platforms have adopted [WGS 84 / Psuedo Mercator -- Spherical Mercator](https://en.wikipedia.org/wiki/Web_Mercator_projection) projected coordinate system, or more commonly known as Web Mercator. When working with most web mapping platforms points are in WGS84 and the APIs do the converting behind the scenes when showing the points in an app. Esri's ArcGIS Runtime platform does things a little differently, taking the Android API as an example, the spatial reference of an `ArcGISMap` is based on the first layer added to the map, all subsequent layers are automatically reprojected to the initial layers spatial reference when needed. \n\nArcGIS online basemaps are in Web Mercator, like other web mapping platforms, and when added as the first layer in an ArcGIS Runtime application it sets Web Mercator as the spatial reference of the map. When getting a location represented as a point from the API you will get the coordinates in Web Mercator. This is different from other mapping APIs. Taking Google Maps as an example whereby the underlying basemap you see in apps is in Web Mercator when you get a location represented as a point it will return WGS84 coordinates to work with the API. While in Google Maps the API handles the reprojection for you, the ArcGIS Runtime API requires the developer to handle to reprojection. \n\nThe ArcGIS Android API provides a `GeometryUtil` class that has a bunch of static methods to allow you to manipulate geometries. One such method for dealing with projections is `GeometryUtil.project()`. This class becomes quite useful when integrating between APIs that use spatial coordinates. \n\nThe ArcGIS Android `MapView` class can give us the center of the area visible to the user as a point in the spatial reference of the map. To work with other spatial APIs you can use the `GeometryUtil.project()` method to convert the point to WGS85.\n\n```kotlin\n// get the center point of the map\nval centerPnt = mapView.visibleArea.extent.center\nval centerPntWGS84 = GeometryEngine.project(centerPnt, SpatialReferences.getWgs84())\n\n// convert lat/lon as String\nval lat = centerPntWGS84.x.toString()\nval lon = centerPntWGS84.y.toString()\n```\n\nWe can use the resulting String representation of the WGS84 latitude and longitude to send to Google's place search API to get a list of predictions based on search input and the center of the map representing the location of where to set a radius around which to search:\n\n```kotlin\n@GET(\"api/place/autocomplete/json\")\nfun getPredictions(@Query(\"key\") apikey: String, \n @Query(\"input\") input: String, \n @Query(\"location\") location: String, \n @Query(\"radius\") radius: String): Call<Prediction>\n```\n\nThe results will be centered around the location you send it instead of null island. \n\nYou can also easily convert screen points to map coordinates in both Google and ArcGIS Android APIs, but they are accessed differently. The Google Maps API for Android provides a `Projection.toScreenLocation()` method to translate between screen location and geographic coordinates, in the ArcGIS Android API this can be done with `MapView.screenToLocation()`. These methods honor the returned point in the projection workflow described above. \n\nCheck out the weather-mpp on [GitHub](https://github.com/doneill/weather-map) for a working example of using Google Places API to zoom to a location result from a places search and map it on an ArcGIS Android Basemap. \n","slug":"spatial-references","published":1,"updated":"2019-05-25T03:46:25.000Z","comments":1,"layout":"post","photos":[],"link":"","_id":"ckz0jkmcz000fnvhwh3mweasz","content":"<p>Modern Web mapping platforms have adopted <a href=\"https://en.wikipedia.org/wiki/Web_Mercator_projection\">WGS 84 / Psuedo Mercator – Spherical Mercator</a> projected coordinate system, or more commonly known as Web Mercator. When working with most web mapping platforms points are in WGS84 and the APIs do the converting behind the scenes when showing the points in an app. Esri’s ArcGIS Runtime platform does things a little differently, taking the Android API as an example, the spatial reference of an <code>ArcGISMap</code> is based on the first layer added to the map, all subsequent layers are automatically reprojected to the initial layers spatial reference when needed. </p>\n<p>ArcGIS online basemaps are in Web Mercator, like other web mapping platforms, and when added as the first layer in an ArcGIS Runtime application it sets Web Mercator as the spatial reference of the map. When getting a location represented as a point from the API you will get the coordinates in Web Mercator. This is different from other mapping APIs. Taking Google Maps as an example whereby the underlying basemap you see in apps is in Web Mercator when you get a location represented as a point it will return WGS84 coordinates to work with the API. While in Google Maps the API handles the reprojection for you, the ArcGIS Runtime API requires the developer to handle to reprojection. </p>\n<p>The ArcGIS Android API provides a <code>GeometryUtil</code> class that has a bunch of static methods to allow you to manipulate geometries. One such method for dealing with projections is <code>GeometryUtil.project()</code>. This class becomes quite useful when integrating between APIs that use spatial coordinates. </p>\n<p>The ArcGIS Android <code>MapView</code> class can give us the center of the area visible to the user as a point in the spatial reference of the map. To work with other spatial APIs you can use the <code>GeometryUtil.project()</code> method to convert the point to WGS85.</p>\n<figure class=\"highlight kotlin\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"comment\">// get the center point of the map</span></span><br><span class=\"line\"><span class=\"keyword\">val</span> centerPnt = mapView.visibleArea.extent.center</span><br><span class=\"line\"><span class=\"keyword\">val</span> centerPntWGS84 = GeometryEngine.project(centerPnt, SpatialReferences.getWgs84())</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"comment\">// convert lat/lon as String</span></span><br><span class=\"line\"><span class=\"keyword\">val</span> lat = centerPntWGS84.x.toString()</span><br><span class=\"line\"><span class=\"keyword\">val</span> lon = centerPntWGS84.y.toString()</span><br></pre></td></tr></table></figure>\n\n<p>We can use the resulting String representation of the WGS84 latitude and longitude to send to Google’s place search API to get a list of predictions based on search input and the center of the map representing the location of where to set a radius around which to search:</p>\n<figure class=\"highlight kotlin\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"meta\">@GET(<span class=\"meta-string\">"api/place/autocomplete/json"</span>)</span></span><br><span class=\"line\"><span class=\"function\"><span class=\"keyword\">fun</span> <span class=\"title\">getPredictions</span><span class=\"params\">(<span class=\"meta\">@Query(<span class=\"meta-string\">"key"</span>)</span> apikey: <span class=\"type\">String</span>, </span></span></span><br><span class=\"line\"><span class=\"params\"><span class=\"function\"> <span class=\"meta\">@Query(<span class=\"meta-string\">"input"</span>)</span> input: <span class=\"type\">String</span>, </span></span></span><br><span class=\"line\"><span class=\"params\"><span class=\"function\"> <span class=\"meta\">@Query(<span class=\"meta-string\">"location"</span>)</span> location: <span class=\"type\">String</span>, </span></span></span><br><span class=\"line\"><span class=\"params\"><span class=\"function\"> <span class=\"meta\">@Query(<span class=\"meta-string\">"radius"</span>)</span> radius: <span class=\"type\">String</span>)</span></span>: Call<Prediction></span><br></pre></td></tr></table></figure>\n\n<p>The results will be centered around the location you send it instead of null island. </p>\n<p>You can also easily convert screen points to map coordinates in both Google and ArcGIS Android APIs, but they are accessed differently. The Google Maps API for Android provides a <code>Projection.toScreenLocation()</code> method to translate between screen location and geographic coordinates, in the ArcGIS Android API this can be done with <code>MapView.screenToLocation()</code>. These methods honor the returned point in the projection workflow described above. </p>\n<p>Check out the weather-mpp on <a href=\"https://github.com/doneill/weather-map\">GitHub</a> for a working example of using Google Places API to zoom to a location result from a places search and map it on an ArcGIS Android Basemap. </p>\n","site":{"data":{"projects":[{"name":"koop-provider-pg","url":"https://www.npmjs.com/package/koop-provider-pg","desc":"Koop provider to query PostGIS spatial data"},{"name":"Map Preview","url":"https://packagecontrol.io/packages/MapPreview","desc":"Preview map layer files in your web browser using Sublime Text 3"},{"name":"kotlin mulitplatform template","url":"https://github.com/doneill/kotlin-multiplatform-template","desc":"Kotlin MPP template to bootstrap Mobile/Web"}]}},"excerpt":"","more":"<p>Modern Web mapping platforms have adopted <a href=\"https://en.wikipedia.org/wiki/Web_Mercator_projection\">WGS 84 / Psuedo Mercator – Spherical Mercator</a> projected coordinate system, or more commonly known as Web Mercator. When working with most web mapping platforms points are in WGS84 and the APIs do the converting behind the scenes when showing the points in an app. Esri’s ArcGIS Runtime platform does things a little differently, taking the Android API as an example, the spatial reference of an <code>ArcGISMap</code> is based on the first layer added to the map, all subsequent layers are automatically reprojected to the initial layers spatial reference when needed. </p>\n<p>ArcGIS online basemaps are in Web Mercator, like other web mapping platforms, and when added as the first layer in an ArcGIS Runtime application it sets Web Mercator as the spatial reference of the map. When getting a location represented as a point from the API you will get the coordinates in Web Mercator. This is different from other mapping APIs. Taking Google Maps as an example whereby the underlying basemap you see in apps is in Web Mercator when you get a location represented as a point it will return WGS84 coordinates to work with the API. While in Google Maps the API handles the reprojection for you, the ArcGIS Runtime API requires the developer to handle to reprojection. </p>\n<p>The ArcGIS Android API provides a <code>GeometryUtil</code> class that has a bunch of static methods to allow you to manipulate geometries. One such method for dealing with projections is <code>GeometryUtil.project()</code>. This class becomes quite useful when integrating between APIs that use spatial coordinates. </p>\n<p>The ArcGIS Android <code>MapView</code> class can give us the center of the area visible to the user as a point in the spatial reference of the map. To work with other spatial APIs you can use the <code>GeometryUtil.project()</code> method to convert the point to WGS85.</p>\n<figure class=\"highlight kotlin\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"comment\">// get the center point of the map</span></span><br><span class=\"line\"><span class=\"keyword\">val</span> centerPnt = mapView.visibleArea.extent.center</span><br><span class=\"line\"><span class=\"keyword\">val</span> centerPntWGS84 = GeometryEngine.project(centerPnt, SpatialReferences.getWgs84())</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"comment\">// convert lat/lon as String</span></span><br><span class=\"line\"><span class=\"keyword\">val</span> lat = centerPntWGS84.x.toString()</span><br><span class=\"line\"><span class=\"keyword\">val</span> lon = centerPntWGS84.y.toString()</span><br></pre></td></tr></table></figure>\n\n<p>We can use the resulting String representation of the WGS84 latitude and longitude to send to Google’s place search API to get a list of predictions based on search input and the center of the map representing the location of where to set a radius around which to search:</p>\n<figure class=\"highlight kotlin\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"meta\">@GET(<span class=\"meta-string\">"api/place/autocomplete/json"</span>)</span></span><br><span class=\"line\"><span class=\"function\"><span class=\"keyword\">fun</span> <span class=\"title\">getPredictions</span><span class=\"params\">(<span class=\"meta\">@Query(<span class=\"meta-string\">"key"</span>)</span> apikey: <span class=\"type\">String</span>, </span></span></span><br><span class=\"line\"><span class=\"params\"><span class=\"function\"> <span class=\"meta\">@Query(<span class=\"meta-string\">"input"</span>)</span> input: <span class=\"type\">String</span>, </span></span></span><br><span class=\"line\"><span class=\"params\"><span class=\"function\"> <span class=\"meta\">@Query(<span class=\"meta-string\">"location"</span>)</span> location: <span class=\"type\">String</span>, </span></span></span><br><span class=\"line\"><span class=\"params\"><span class=\"function\"> <span class=\"meta\">@Query(<span class=\"meta-string\">"radius"</span>)</span> radius: <span class=\"type\">String</span>)</span></span>: Call<Prediction></span><br></pre></td></tr></table></figure>\n\n<p>The results will be centered around the location you send it instead of null island. </p>\n<p>You can also easily convert screen points to map coordinates in both Google and ArcGIS Android APIs, but they are accessed differently. The Google Maps API for Android provides a <code>Projection.toScreenLocation()</code> method to translate between screen location and geographic coordinates, in the ArcGIS Android API this can be done with <code>MapView.screenToLocation()</code>. These methods honor the returned point in the projection workflow described above. </p>\n<p>Check out the weather-mpp on <a href=\"https://github.com/doneill/weather-map\">GitHub</a> for a working example of using Google Places API to zoom to a location result from a places search and map it on an ArcGIS Android Basemap. </p>\n"},{"title":"Koop PostGIS Provider","date":"2020-10-05T01:18:25.000Z","_content":"\n[Koop](https://koopjs.github.io) uses providers to transform data from one spatial format to another by converting the input data to GeoJSON and using output plugins to transform to various outputs, one officially supported output is [GeoServices](https://geoservices.github.io/). The [koop provider pg](https://github.com/doneill/koop-provider-pg) uses PostGIS Spatial data as the input data to be transformed allowing ArcGIS clients access to the data.\n\n## Update 11/06/2020\nThis post is about working with the Koop PostGIS provider from a developer perspective to configure the provider for your databse configuration. Recent updates have allowed end users to configure the database without requiring developer access by setting the following PostgreSQL database environment variables:\n\n```\nPG_HOST=<host>\nPG_PORT=<port>\nPG_DATABASE=<database>\nPG_USER=<user>\nPG_PASSWORD=<password>\n```\n\nThis allows for the provider to be published to [npm package respository](https://www.npmjs.com/package/koop-provider-pg) and installed with your Koop application using the [Koop CLI](https://koopjs.github.io/docs/usage/provider#koop-cli):\n\n```\n=> koop add provider koop-provider-pg\n```\n\nContinue reading about configuring the provider from source and/or working with the data once you have the provider configured.\n\n## Database configuration\nTo get started using the **pg** provider and work with your PostGIS data in ArcGIS clients you need clone the [repo](https://github.com/doneill/koop-provider-pg) and setup your database connection.\n\n- Open the **config/default.json** file and set the connection parameters as follows:\n\n```json\n{\n \"db\": {\n \"host\": \"host\",\n \"port\": 5432,\n \"database\": \"dbname\",\n \"user\": \"user\",\n \"password\": \"password\"\n }\n}\n```\n\nThe provider will now use this connection to query the data.\n\n## Start the server\nOnce we have a database connection configured we can run the server to generate output routes to the connection PostGIS data provided by the **pg** provider. Koop provides a wrapper around an Express.js server to listen to incoming requests. From the root of the repo project we can install the required npm modules and start the server with the following:\n\n```bash\n# install modules\n=> npm start\n# start the server\n=> npm start\n```\n\n## Service endpoint format\nThe Koop service endpoint format is as follows: `{koop-server-instance}/{provider-framework}/{output-type}`. For the **pg** provider those are defined by the following:\n\n- koop server instance: http://localhost:8080\n- provider fragment: /pg/rest/services/`${schema}.${table}`\n- output fragment: FeatureServer/0\n\nWhere you provide the `${schema}.${table}` of the data you want to work with.\n\n## Generate GeoJSON\nKoop providers responsibilities include formatting requrests to the data source, in this case PostGIS, and convert the data to GeoJSON. PostGIS can return the geometry as a GeoJSON feature object using [ST_AsGeoJSON](https://postgis.net/docs/ST_AsGeoJSON.html). To build a feature collection the **pg** provider uses the following query:\n\n```sql\nSELECT jsonb_build_object(\n 'type', 'FeatureCollection',\n 'features', jsonb_agg(features.feature)\n) FROM (\n SELECT jsonb_build_object(\n 'type', 'Feature',\n 'id', gid,\n 'geometry', ST_AsGeoJSON(ST_Transform(geom,4326))::jsonb,\n 'properties', to_jsonb(inputs) - 'gid' - 'geom'\n ) AS feature\n FROM (SELECT * FROM $[table:raw]) inputs) features;\n```\n\nThe `$[table:raw]` is provided by the **pg** provider fragment\n\n## Working with the data\nNow that we have defined the url schema, we can start working with the data in various GeoService clients.\n\n### ArcGIS Online WebMap\nAn easy way to confirm the data is working is by opening it up in a webmap provided by ArcGIS Online. You can use the `url` parameter to get access to the feature service and visualize it in a browser.\n\n`https://www.arcgis.com/home/webmap/viewer.html?url=http://localhost:8080/pg/${schema}.${table}/FeatureServer/`\n\n\n\n### Esri Leaflet\nThere is an example web application provided in the repo in the **src/view/** folder where you can replace the `${schema}.${table}` in *map.js* to see the layer rendered in an Esri Leaflet app.\n\n```javascript\n// esri leaflet feature layer\nL.esri.featureLayer({\n url: 'http://localhost:8080/pg/${schema}.${table}/FeatureServer/layers'\n }).addTo(map);\n```\n\n\n\n### QGIS\nYou can add ArcGIS Feature Services in QGIS using the **Data Source Manager > ArcGIS Feature Service > New Connection**. Name the layer appropriately and paste your Koop pg url with the following format:\n\n`http://localhost:8080/pg/rest/services/${schema}.${table}/FeatureServer`\n\nAfter saving the connection by pressing **Ok** you should see the feature layer listed once connected to the service. You can then add the layer to your map.\n\n\n\n### Query\nThe query operation is supported on Koop services and you can use the result in any of the clients listed above. Below is an example query where you can define the **outFields** with comma separated `${fields-2-return}`:\n\n`http://localhost:8080/pg/rest/services/${schema}.${table}/FeatureServer/0/query?where=1=1&outFields=${fields-2-return}&returnGeometry=true&f=json`\n\n## Moving forward\nDive deeper into the source in the [koop provider pg repo](https://github.com/doneill/koop-provider-pg) and read more about koop providers [here](https://koopjs.github.io/docs/usage/provider). Future considerations to combine this provider with a web application to display a catalog of feature services provided by the PostGIS database connection.\n","source":"_posts/pgkoop.md","raw":"---\ntitle: Koop PostGIS Provider\ndate: 2020-10-04 18:18:25\ntags:\n- esri\n- geoservices\n- arcgis\n- geojson\n- postgis\n- leaflet\n---\n\n[Koop](https://koopjs.github.io) uses providers to transform data from one spatial format to another by converting the input data to GeoJSON and using output plugins to transform to various outputs, one officially supported output is [GeoServices](https://geoservices.github.io/). The [koop provider pg](https://github.com/doneill/koop-provider-pg) uses PostGIS Spatial data as the input data to be transformed allowing ArcGIS clients access to the data.\n\n## Update 11/06/2020\nThis post is about working with the Koop PostGIS provider from a developer perspective to configure the provider for your databse configuration. Recent updates have allowed end users to configure the database without requiring developer access by setting the following PostgreSQL database environment variables:\n\n```\nPG_HOST=<host>\nPG_PORT=<port>\nPG_DATABASE=<database>\nPG_USER=<user>\nPG_PASSWORD=<password>\n```\n\nThis allows for the provider to be published to [npm package respository](https://www.npmjs.com/package/koop-provider-pg) and installed with your Koop application using the [Koop CLI](https://koopjs.github.io/docs/usage/provider#koop-cli):\n\n```\n=> koop add provider koop-provider-pg\n```\n\nContinue reading about configuring the provider from source and/or working with the data once you have the provider configured.\n\n## Database configuration\nTo get started using the **pg** provider and work with your PostGIS data in ArcGIS clients you need clone the [repo](https://github.com/doneill/koop-provider-pg) and setup your database connection.\n\n- Open the **config/default.json** file and set the connection parameters as follows:\n\n```json\n{\n \"db\": {\n \"host\": \"host\",\n \"port\": 5432,\n \"database\": \"dbname\",\n \"user\": \"user\",\n \"password\": \"password\"\n }\n}\n```\n\nThe provider will now use this connection to query the data.\n\n## Start the server\nOnce we have a database connection configured we can run the server to generate output routes to the connection PostGIS data provided by the **pg** provider. Koop provides a wrapper around an Express.js server to listen to incoming requests. From the root of the repo project we can install the required npm modules and start the server with the following:\n\n```bash\n# install modules\n=> npm start\n# start the server\n=> npm start\n```\n\n## Service endpoint format\nThe Koop service endpoint format is as follows: `{koop-server-instance}/{provider-framework}/{output-type}`. For the **pg** provider those are defined by the following:\n\n- koop server instance: http://localhost:8080\n- provider fragment: /pg/rest/services/`${schema}.${table}`\n- output fragment: FeatureServer/0\n\nWhere you provide the `${schema}.${table}` of the data you want to work with.\n\n## Generate GeoJSON\nKoop providers responsibilities include formatting requrests to the data source, in this case PostGIS, and convert the data to GeoJSON. PostGIS can return the geometry as a GeoJSON feature object using [ST_AsGeoJSON](https://postgis.net/docs/ST_AsGeoJSON.html). To build a feature collection the **pg** provider uses the following query:\n\n```sql\nSELECT jsonb_build_object(\n 'type', 'FeatureCollection',\n 'features', jsonb_agg(features.feature)\n) FROM (\n SELECT jsonb_build_object(\n 'type', 'Feature',\n 'id', gid,\n 'geometry', ST_AsGeoJSON(ST_Transform(geom,4326))::jsonb,\n 'properties', to_jsonb(inputs) - 'gid' - 'geom'\n ) AS feature\n FROM (SELECT * FROM $[table:raw]) inputs) features;\n```\n\nThe `$[table:raw]` is provided by the **pg** provider fragment\n\n## Working with the data\nNow that we have defined the url schema, we can start working with the data in various GeoService clients.\n\n### ArcGIS Online WebMap\nAn easy way to confirm the data is working is by opening it up in a webmap provided by ArcGIS Online. You can use the `url` parameter to get access to the feature service and visualize it in a browser.\n\n`https://www.arcgis.com/home/webmap/viewer.html?url=http://localhost:8080/pg/${schema}.${table}/FeatureServer/`\n\n\n\n### Esri Leaflet\nThere is an example web application provided in the repo in the **src/view/** folder where you can replace the `${schema}.${table}` in *map.js* to see the layer rendered in an Esri Leaflet app.\n\n```javascript\n// esri leaflet feature layer\nL.esri.featureLayer({\n url: 'http://localhost:8080/pg/${schema}.${table}/FeatureServer/layers'\n }).addTo(map);\n```\n\n\n\n### QGIS\nYou can add ArcGIS Feature Services in QGIS using the **Data Source Manager > ArcGIS Feature Service > New Connection**. Name the layer appropriately and paste your Koop pg url with the following format:\n\n`http://localhost:8080/pg/rest/services/${schema}.${table}/FeatureServer`\n\nAfter saving the connection by pressing **Ok** you should see the feature layer listed once connected to the service. You can then add the layer to your map.\n\n\n\n### Query\nThe query operation is supported on Koop services and you can use the result in any of the clients listed above. Below is an example query where you can define the **outFields** with comma separated `${fields-2-return}`:\n\n`http://localhost:8080/pg/rest/services/${schema}.${table}/FeatureServer/0/query?where=1=1&outFields=${fields-2-return}&returnGeometry=true&f=json`\n\n## Moving forward\nDive deeper into the source in the [koop provider pg repo](https://github.com/doneill/koop-provider-pg) and read more about koop providers [here](https://koopjs.github.io/docs/usage/provider). Future considerations to combine this provider with a web application to display a catalog of feature services provided by the PostGIS database connection.\n","slug":"pgkoop","published":1,"updated":"2020-10-05T01:18:25.000Z","comments":1,"layout":"post","photos":[],"link":"","_id":"ckz0jkmd0000hnvhwd584gwmz","content":"<p><a href=\"https://koopjs.github.io/\">Koop</a> uses providers to transform data from one spatial format to another by converting the input data to GeoJSON and using output plugins to transform to various outputs, one officially supported output is <a href=\"https://geoservices.github.io/\">GeoServices</a>. The <a href=\"https://github.com/doneill/koop-provider-pg\">koop provider pg</a> uses PostGIS Spatial data as the input data to be transformed allowing ArcGIS clients access to the data.</p>\n<h2 id=\"Update-11-06-2020\"><a href=\"#Update-11-06-2020\" class=\"headerlink\" title=\"Update 11/06/2020\"></a>Update 11/06/2020</h2><p>This post is about working with the Koop PostGIS provider from a developer perspective to configure the provider for your databse configuration. Recent updates have allowed end users to configure the database without requiring developer access by setting the following PostgreSQL database environment variables:</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">PG_HOST=<host></span><br><span class=\"line\">PG_PORT=<port></span><br><span class=\"line\">PG_DATABASE=<database></span><br><span class=\"line\">PG_USER=<user></span><br><span class=\"line\">PG_PASSWORD=<password></span><br></pre></td></tr></table></figure>\n\n<p>This allows for the provider to be published to <a href=\"https://www.npmjs.com/package/koop-provider-pg\">npm package respository</a> and installed with your Koop application using the <a href=\"https://koopjs.github.io/docs/usage/provider#koop-cli\">Koop CLI</a>:</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">=> koop add provider koop-provider-pg</span><br></pre></td></tr></table></figure>\n\n<p>Continue reading about configuring the provider from source and/or working with the data once you have the provider configured.</p>\n<h2 id=\"Database-configuration\"><a href=\"#Database-configuration\" class=\"headerlink\" title=\"Database configuration\"></a>Database configuration</h2><p>To get started using the <strong>pg</strong> provider and work with your PostGIS data in ArcGIS clients you need clone the <a href=\"https://github.com/doneill/koop-provider-pg\">repo</a> and setup your database connection.</p>\n<ul>\n<li>Open the <strong>config/default.json</strong> file and set the connection parameters as follows:</li>\n</ul>\n<figure class=\"highlight json\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br><span class=\"line\">8</span><br><span class=\"line\">9</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">{</span><br><span class=\"line\"> <span class=\"attr\">"db"</span>: {</span><br><span class=\"line\"> <span class=\"attr\">"host"</span>: <span class=\"string\">"host"</span>,</span><br><span class=\"line\"> <span class=\"attr\">"port"</span>: <span class=\"number\">5432</span>,</span><br><span class=\"line\"> <span class=\"attr\">"database"</span>: <span class=\"string\">"dbname"</span>,</span><br><span class=\"line\"> <span class=\"attr\">"user"</span>: <span class=\"string\">"user"</span>,</span><br><span class=\"line\"> <span class=\"attr\">"password"</span>: <span class=\"string\">"password"</span></span><br><span class=\"line\"> }</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n\n<p>The provider will now use this connection to query the data.</p>\n<h2 id=\"Start-the-server\"><a href=\"#Start-the-server\" class=\"headerlink\" title=\"Start the server\"></a>Start the server</h2><p>Once we have a database connection configured we can run the server to generate output routes to the connection PostGIS data provided by the <strong>pg</strong> provider. Koop provides a wrapper around an Express.js server to listen to incoming requests. From the root of the repo project we can install the required npm modules and start the server with the following:</p>\n<figure class=\"highlight bash\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"comment\"># install modules</span></span><br><span class=\"line\">=> npm start</span><br><span class=\"line\"><span class=\"comment\"># start the server</span></span><br><span class=\"line\">=> npm start</span><br></pre></td></tr></table></figure>\n\n<h2 id=\"Service-endpoint-format\"><a href=\"#Service-endpoint-format\" class=\"headerlink\" title=\"Service endpoint format\"></a>Service endpoint format</h2><p>The Koop service endpoint format is as follows: <code>{koop-server-instance}/{provider-framework}/{output-type}</code>. For the <strong>pg</strong> provider those are defined by the following:</p>\n<ul>\n<li>koop server instance: <a href=\"http://localhost:8080/\">http://localhost:8080</a></li>\n<li>provider fragment: /pg/rest/services/<code>${schema}.${table}</code></li>\n<li>output fragment: FeatureServer/0</li>\n</ul>\n<p>Where you provide the <code>${schema}.${table}</code> of the data you want to work with.</p>\n<h2 id=\"Generate-GeoJSON\"><a href=\"#Generate-GeoJSON\" class=\"headerlink\" title=\"Generate GeoJSON\"></a>Generate GeoJSON</h2><p>Koop providers responsibilities include formatting requrests to the data source, in this case PostGIS, and convert the data to GeoJSON. PostGIS can return the geometry as a GeoJSON feature object using <a href=\"https://postgis.net/docs/ST_AsGeoJSON.html\">ST_AsGeoJSON</a>. To build a feature collection the <strong>pg</strong> provider uses the following query:</p>\n<figure class=\"highlight sql\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br><span class=\"line\">8</span><br><span class=\"line\">9</span><br><span class=\"line\">10</span><br><span class=\"line\">11</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">SELECT</span> jsonb_build_object(</span><br><span class=\"line\"> <span class=\"string\">'type'</span>, <span class=\"string\">'FeatureCollection'</span>,</span><br><span class=\"line\"> <span class=\"string\">'features'</span>, jsonb_agg(features.feature)</span><br><span class=\"line\">) <span class=\"keyword\">FROM</span> (</span><br><span class=\"line\"> <span class=\"keyword\">SELECT</span> jsonb_build_object(</span><br><span class=\"line\"> <span class=\"string\">'type'</span>, <span class=\"string\">'Feature'</span>,</span><br><span class=\"line\"> <span class=\"string\">'id'</span>, gid,</span><br><span class=\"line\"> <span class=\"string\">'geometry'</span>, ST_AsGeoJSON(ST_Transform(geom,<span class=\"number\">4326</span>))::jsonb,</span><br><span class=\"line\"> <span class=\"string\">'properties'</span>, to_jsonb(inputs) <span class=\"operator\">-</span> <span class=\"string\">'gid'</span> <span class=\"operator\">-</span> <span class=\"string\">'geom'</span></span><br><span class=\"line\"> ) <span class=\"keyword\">AS</span> feature</span><br><span class=\"line\"> <span class=\"keyword\">FROM</span> (<span class=\"keyword\">SELECT</span> <span class=\"operator\">*</span> <span class=\"keyword\">FROM</span> $[<span class=\"keyword\">table</span>:raw]) inputs) features;</span><br></pre></td></tr></table></figure>\n\n<p>The <code>$[table:raw]</code> is provided by the <strong>pg</strong> provider fragment</p>\n<h2 id=\"Working-with-the-data\"><a href=\"#Working-with-the-data\" class=\"headerlink\" title=\"Working with the data\"></a>Working with the data</h2><p>Now that we have defined the url schema, we can start working with the data in various GeoService clients.</p>\n<h3 id=\"ArcGIS-Online-WebMap\"><a href=\"#ArcGIS-Online-WebMap\" class=\"headerlink\" title=\"ArcGIS Online WebMap\"></a>ArcGIS Online WebMap</h3><p>An easy way to confirm the data is working is by opening it up in a webmap provided by ArcGIS Online. You can use the <code>url</code> parameter to get access to the feature service and visualize it in a browser.</p>\n<p><code>https://www.arcgis.com/home/webmap/viewer.html?url=http://localhost:8080/pg/${schema}.${table}/FeatureServer/</code></p>\n<p><img src=\"/2020/10/04/pgkoop/agol.png\" alt=\"AGOL\" title=\"PostGIS Geoservice in AGOL\"></p>\n<h3 id=\"Esri-Leaflet\"><a href=\"#Esri-Leaflet\" class=\"headerlink\" title=\"Esri Leaflet\"></a>Esri Leaflet</h3><p>There is an example web application provided in the repo in the <strong>src/view/</strong> folder where you can replace the <code>${schema}.${table}</code> in <em>map.js</em> to see the layer rendered in an Esri Leaflet app.</p>\n<figure class=\"highlight javascript\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"comment\">// esri leaflet feature layer</span></span><br><span class=\"line\">L.esri.featureLayer({</span><br><span class=\"line\"> <span class=\"attr\">url</span>: <span class=\"string\">'http://localhost:8080/pg/${schema}.${table}/FeatureServer/layers'</span></span><br><span class=\"line\"> }).addTo(map);</span><br></pre></td></tr></table></figure>\n\n<p><img src=\"/2020/10/04/pgkoop/leaflet.png\" alt=\"Esri Leaflet\" title=\"PostGIS Geoservice in Esri Leaflet\"></p>\n<h3 id=\"QGIS\"><a href=\"#QGIS\" class=\"headerlink\" title=\"QGIS\"></a>QGIS</h3><p>You can add ArcGIS Feature Services in QGIS using the <strong>Data Source Manager > ArcGIS Feature Service > New Connection</strong>. Name the layer appropriately and paste your Koop pg url with the following format:</p>\n<p><code>http://localhost:8080/pg/rest/services/${schema}.${table}/FeatureServer</code></p>\n<p>After saving the connection by pressing <strong>Ok</strong> you should see the feature layer listed once connected to the service. You can then add the layer to your map.</p>\n<p><img src=\"/2020/10/04/pgkoop/qgis.png\" alt=\"QGIS\" title=\"PostGIS Geoservice in QGIS\"></p>\n<h3 id=\"Query\"><a href=\"#Query\" class=\"headerlink\" title=\"Query\"></a>Query</h3><p>The query operation is supported on Koop services and you can use the result in any of the clients listed above. Below is an example query where you can define the <strong>outFields</strong> with comma separated <code>${fields-2-return}</code>:</p>\n<p><code>http://localhost:8080/pg/rest/services/${schema}.${table}/FeatureServer/0/query?where=1=1&outFields=${fields-2-return}&returnGeometry=true&f=json</code></p>\n<h2 id=\"Moving-forward\"><a href=\"#Moving-forward\" class=\"headerlink\" title=\"Moving forward\"></a>Moving forward</h2><p>Dive deeper into the source in the <a href=\"https://github.com/doneill/koop-provider-pg\">koop provider pg repo</a> and read more about koop providers <a href=\"https://koopjs.github.io/docs/usage/provider\">here</a>. Future considerations to combine this provider with a web application to display a catalog of feature services provided by the PostGIS database connection.</p>\n","site":{"data":{"projects":[{"name":"koop-provider-pg","url":"https://www.npmjs.com/package/koop-provider-pg","desc":"Koop provider to query PostGIS spatial data"},{"name":"Map Preview","url":"https://packagecontrol.io/packages/MapPreview","desc":"Preview map layer files in your web browser using Sublime Text 3"},{"name":"kotlin mulitplatform template","url":"https://github.com/doneill/kotlin-multiplatform-template","desc":"Kotlin MPP template to bootstrap Mobile/Web"}]}},"excerpt":"","more":"<p><a href=\"https://koopjs.github.io/\">Koop</a> uses providers to transform data from one spatial format to another by converting the input data to GeoJSON and using output plugins to transform to various outputs, one officially supported output is <a href=\"https://geoservices.github.io/\">GeoServices</a>. The <a href=\"https://github.com/doneill/koop-provider-pg\">koop provider pg</a> uses PostGIS Spatial data as the input data to be transformed allowing ArcGIS clients access to the data.</p>\n<h2 id=\"Update-11-06-2020\"><a href=\"#Update-11-06-2020\" class=\"headerlink\" title=\"Update 11/06/2020\"></a>Update 11/06/2020</h2><p>This post is about working with the Koop PostGIS provider from a developer perspective to configure the provider for your databse configuration. Recent updates have allowed end users to configure the database without requiring developer access by setting the following PostgreSQL database environment variables:</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">PG_HOST=<host></span><br><span class=\"line\">PG_PORT=<port></span><br><span class=\"line\">PG_DATABASE=<database></span><br><span class=\"line\">PG_USER=<user></span><br><span class=\"line\">PG_PASSWORD=<password></span><br></pre></td></tr></table></figure>\n\n<p>This allows for the provider to be published to <a href=\"https://www.npmjs.com/package/koop-provider-pg\">npm package respository</a> and installed with your Koop application using the <a href=\"https://koopjs.github.io/docs/usage/provider#koop-cli\">Koop CLI</a>:</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">=> koop add provider koop-provider-pg</span><br></pre></td></tr></table></figure>\n\n<p>Continue reading about configuring the provider from source and/or working with the data once you have the provider configured.</p>\n<h2 id=\"Database-configuration\"><a href=\"#Database-configuration\" class=\"headerlink\" title=\"Database configuration\"></a>Database configuration</h2><p>To get started using the <strong>pg</strong> provider and work with your PostGIS data in ArcGIS clients you need clone the <a href=\"https://github.com/doneill/koop-provider-pg\">repo</a> and setup your database connection.</p>\n<ul>\n<li>Open the <strong>config/default.json</strong> file and set the connection parameters as follows:</li>\n</ul>\n<figure class=\"highlight json\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br><span class=\"line\">8</span><br><span class=\"line\">9</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">{</span><br><span class=\"line\"> <span class=\"attr\">"db"</span>: {</span><br><span class=\"line\"> <span class=\"attr\">"host"</span>: <span class=\"string\">"host"</span>,</span><br><span class=\"line\"> <span class=\"attr\">"port"</span>: <span class=\"number\">5432</span>,</span><br><span class=\"line\"> <span class=\"attr\">"database"</span>: <span class=\"string\">"dbname"</span>,</span><br><span class=\"line\"> <span class=\"attr\">"user"</span>: <span class=\"string\">"user"</span>,</span><br><span class=\"line\"> <span class=\"attr\">"password"</span>: <span class=\"string\">"password"</span></span><br><span class=\"line\"> }</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n\n<p>The provider will now use this connection to query the data.</p>\n<h2 id=\"Start-the-server\"><a href=\"#Start-the-server\" class=\"headerlink\" title=\"Start the server\"></a>Start the server</h2><p>Once we have a database connection configured we can run the server to generate output routes to the connection PostGIS data provided by the <strong>pg</strong> provider. Koop provides a wrapper around an Express.js server to listen to incoming requests. From the root of the repo project we can install the required npm modules and start the server with the following:</p>\n<figure class=\"highlight bash\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"comment\"># install modules</span></span><br><span class=\"line\">=> npm start</span><br><span class=\"line\"><span class=\"comment\"># start the server</span></span><br><span class=\"line\">=> npm start</span><br></pre></td></tr></table></figure>\n\n<h2 id=\"Service-endpoint-format\"><a href=\"#Service-endpoint-format\" class=\"headerlink\" title=\"Service endpoint format\"></a>Service endpoint format</h2><p>The Koop service endpoint format is as follows: <code>{koop-server-instance}/{provider-framework}/{output-type}</code>. For the <strong>pg</strong> provider those are defined by the following:</p>\n<ul>\n<li>koop server instance: <a href=\"http://localhost:8080/\">http://localhost:8080</a></li>\n<li>provider fragment: /pg/rest/services/<code>${schema}.${table}</code></li>\n<li>output fragment: FeatureServer/0</li>\n</ul>\n<p>Where you provide the <code>${schema}.${table}</code> of the data you want to work with.</p>\n<h2 id=\"Generate-GeoJSON\"><a href=\"#Generate-GeoJSON\" class=\"headerlink\" title=\"Generate GeoJSON\"></a>Generate GeoJSON</h2><p>Koop providers responsibilities include formatting requrests to the data source, in this case PostGIS, and convert the data to GeoJSON. PostGIS can return the geometry as a GeoJSON feature object using <a href=\"https://postgis.net/docs/ST_AsGeoJSON.html\">ST_AsGeoJSON</a>. To build a feature collection the <strong>pg</strong> provider uses the following query:</p>\n<figure class=\"highlight sql\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br><span class=\"line\">8</span><br><span class=\"line\">9</span><br><span class=\"line\">10</span><br><span class=\"line\">11</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">SELECT</span> jsonb_build_object(</span><br><span class=\"line\"> <span class=\"string\">'type'</span>, <span class=\"string\">'FeatureCollection'</span>,</span><br><span class=\"line\"> <span class=\"string\">'features'</span>, jsonb_agg(features.feature)</span><br><span class=\"line\">) <span class=\"keyword\">FROM</span> (</span><br><span class=\"line\"> <span class=\"keyword\">SELECT</span> jsonb_build_object(</span><br><span class=\"line\"> <span class=\"string\">'type'</span>, <span class=\"string\">'Feature'</span>,</span><br><span class=\"line\"> <span class=\"string\">'id'</span>, gid,</span><br><span class=\"line\"> <span class=\"string\">'geometry'</span>, ST_AsGeoJSON(ST_Transform(geom,<span class=\"number\">4326</span>))::jsonb,</span><br><span class=\"line\"> <span class=\"string\">'properties'</span>, to_jsonb(inputs) <span class=\"operator\">-</span> <span class=\"string\">'gid'</span> <span class=\"operator\">-</span> <span class=\"string\">'geom'</span></span><br><span class=\"line\"> ) <span class=\"keyword\">AS</span> feature</span><br><span class=\"line\"> <span class=\"keyword\">FROM</span> (<span class=\"keyword\">SELECT</span> <span class=\"operator\">*</span> <span class=\"keyword\">FROM</span> $[<span class=\"keyword\">table</span>:raw]) inputs) features;</span><br></pre></td></tr></table></figure>\n\n<p>The <code>$[table:raw]</code> is provided by the <strong>pg</strong> provider fragment</p>\n<h2 id=\"Working-with-the-data\"><a href=\"#Working-with-the-data\" class=\"headerlink\" title=\"Working with the data\"></a>Working with the data</h2><p>Now that we have defined the url schema, we can start working with the data in various GeoService clients.</p>\n<h3 id=\"ArcGIS-Online-WebMap\"><a href=\"#ArcGIS-Online-WebMap\" class=\"headerlink\" title=\"ArcGIS Online WebMap\"></a>ArcGIS Online WebMap</h3><p>An easy way to confirm the data is working is by opening it up in a webmap provided by ArcGIS Online. You can use the <code>url</code> parameter to get access to the feature service and visualize it in a browser.</p>\n<p><code>https://www.arcgis.com/home/webmap/viewer.html?url=http://localhost:8080/pg/${schema}.${table}/FeatureServer/</code></p>\n<p><img src=\"/2020/10/04/pgkoop/agol.png\" alt=\"AGOL\" title=\"PostGIS Geoservice in AGOL\"></p>\n<h3 id=\"Esri-Leaflet\"><a href=\"#Esri-Leaflet\" class=\"headerlink\" title=\"Esri Leaflet\"></a>Esri Leaflet</h3><p>There is an example web application provided in the repo in the <strong>src/view/</strong> folder where you can replace the <code>${schema}.${table}</code> in <em>map.js</em> to see the layer rendered in an Esri Leaflet app.</p>\n<figure class=\"highlight javascript\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"comment\">// esri leaflet feature layer</span></span><br><span class=\"line\">L.esri.featureLayer({</span><br><span class=\"line\"> <span class=\"attr\">url</span>: <span class=\"string\">'http://localhost:8080/pg/${schema}.${table}/FeatureServer/layers'</span></span><br><span class=\"line\"> }).addTo(map);</span><br></pre></td></tr></table></figure>\n\n<p><img src=\"/2020/10/04/pgkoop/leaflet.png\" alt=\"Esri Leaflet\" title=\"PostGIS Geoservice in Esri Leaflet\"></p>\n<h3 id=\"QGIS\"><a href=\"#QGIS\" class=\"headerlink\" title=\"QGIS\"></a>QGIS</h3><p>You can add ArcGIS Feature Services in QGIS using the <strong>Data Source Manager > ArcGIS Feature Service > New Connection</strong>. Name the layer appropriately and paste your Koop pg url with the following format:</p>\n<p><code>http://localhost:8080/pg/rest/services/${schema}.${table}/FeatureServer</code></p>\n<p>After saving the connection by pressing <strong>Ok</strong> you should see the feature layer listed once connected to the service. You can then add the layer to your map.</p>\n<p><img src=\"/2020/10/04/pgkoop/qgis.png\" alt=\"QGIS\" title=\"PostGIS Geoservice in QGIS\"></p>\n<h3 id=\"Query\"><a href=\"#Query\" class=\"headerlink\" title=\"Query\"></a>Query</h3><p>The query operation is supported on Koop services and you can use the result in any of the clients listed above. Below is an example query where you can define the <strong>outFields</strong> with comma separated <code>${fields-2-return}</code>:</p>\n<p><code>http://localhost:8080/pg/rest/services/${schema}.${table}/FeatureServer/0/query?where=1=1&outFields=${fields-2-return}&returnGeometry=true&f=json</code></p>\n<h2 id=\"Moving-forward\"><a href=\"#Moving-forward\" class=\"headerlink\" title=\"Moving forward\"></a>Moving forward</h2><p>Dive deeper into the source in the <a href=\"https://github.com/doneill/koop-provider-pg\">koop provider pg repo</a> and read more about koop providers <a href=\"https://koopjs.github.io/docs/usage/provider\">here</a>. Future considerations to combine this provider with a web application to display a catalog of feature services provided by the PostGIS database connection.</p>\n"},{"title":"Koop PostGIS Vector Tiles","date":"2021-06-14T01:18:25.000Z","_content":"\nI introduced the [Koop PostGIS provider](https://github.com/doneill/koop-provider-pg) from a developer perspective in a previous [post](https://gh.jdoneill.com/2020/10/04/pgkoop/) where the output is GeoServices. This post will guide us through installing a Koop service with support for PostGIS input and Vector Tiles as an output format.\n\n## Update 09/12/2021\nReplaced ArcGIS Oceans Basemap with Dark Gray Vector Basemap in the client javascript example.\n\n## Install\nKoop providers require you to first install [Koop](https://koopjs.github.io/). You can add `koop-provider-pg` to your Koop server dependencies by installing it with npm:\n\n```bash\n# install koop cli using npm\n$ npm install -g @koopjs/cli\n\n# create a new koop app\n$ koop new app pg-koop\n\n# cd into the pg-koop app\n$ cd pg-koop\n\n# install provider\n$ koop add provider koop-provider-pg\n\n```\n\nThis will allow you to serve your PostGIS data as GeoServices. You can add `@koopjs/output-vector-tiles` to generate `pbf` vector tiles as output:\n\n```bash\n$ koop add output @koopjs/output-vector-tiles\n```\n\n## Start the Koop server\n\n```bash\n# start koop server\n$ koop serve\n```\n\n## Vector Tiles\nThe server will now support both GeoServices and Vector Tiles as output formats to your PostGIS input data. Below is a list of the output routes for vector tiles:\n\n```bash\n# :id = ${schema}.$(table)\n\n\"VectorTileServer\" output routes for the \"pg\" provider Methods\n--------------------------------------------------------------------------- ---------\n/pg/:id/VectorTileServer/:z([0-9]+)/:x([0-9]+)/:y([0-9]+).pbf GET\n/pg/:id/VectorTileServer/tiles.json GET\n/pg/rest/services/:id/VectorTileServer/:z([0-9]+)/:x([0-9]+)/:y([0-9]+).pbf GET\n/pg/rest/services/:id/VectorTileServer GET, POST\n/pg/rest/services/:id/VectorTileServer GET, POST\n/pg/rest/services/:id/VectorTileServer/resources/styles/root.json GET\n```\n\nYour data can now be viewed in clients that support [Vector Tiles](https://github.com/koopjs/koop-output-vector-tiles). Below is an example using [ArcGIS API for Javascript](https://developers.arcgis.com/javascript/latest/) creating a [VectorTileLayer class](https://developers.arcgis.com/javascript/latest/api-reference/esri-layers-VectorTileLayer.html) with the service URL to display the data. Replace `${schema}.${table}` with schema.table from your PostGIS db to view:\n\n```javascript\n require([\"esri/Map\", \"esri/views/MapView\", \"esri/layers/VectorTileLayer\"], function(\n Map,\n MapView,\n VectorTileLayer\n ) {\n // Create a Map\n const map = new Map({\n basemap: \"dark-gray-vector\"\n });\n // Make map view and bind it to the map\n var view = new MapView({\n container: \"viewDiv\",\n map: map,\n center: [-122.31082,47.60606],\n zoom: 12\n });\n // add vector tile layer from pg-koop\n var tileLayer = new VectorTileLayer({\n url:\n \"http://localhost:8080/pg/rest/services/${schema}.${table}/VectorTileServer\"\n });\n map.add(tileLayer);\n });\n```\nGenerating the following ouput of PostGIS point data over the [Dark Gray Vector Basemap](https://www.arcgis.com/home/item.html?id=c11ce4f7801740b2905eb03ddc963ac8)\n\n\n\nYou can view the example in the [Koop PostGIS provider source](https://github.com/doneill/koop-provider-pg/blob/main/src/view/vector-tile.html).\n","source":"_posts/koop-vector-tile.md","raw":"---\ntitle: Koop PostGIS Vector Tiles\ndate: 2021-06-13 18:18:25\ntags:\n- esri\n- vector tiles\n- arcgis\n- geojson\n- postgis\n---\n\nI introduced the [Koop PostGIS provider](https://github.com/doneill/koop-provider-pg) from a developer perspective in a previous [post](https://gh.jdoneill.com/2020/10/04/pgkoop/) where the output is GeoServices. This post will guide us through installing a Koop service with support for PostGIS input and Vector Tiles as an output format.\n\n## Update 09/12/2021\nReplaced ArcGIS Oceans Basemap with Dark Gray Vector Basemap in the client javascript example.\n\n## Install\nKoop providers require you to first install [Koop](https://koopjs.github.io/). You can add `koop-provider-pg` to your Koop server dependencies by installing it with npm:\n\n```bash\n# install koop cli using npm\n$ npm install -g @koopjs/cli\n\n# create a new koop app\n$ koop new app pg-koop\n\n# cd into the pg-koop app\n$ cd pg-koop\n\n# install provider\n$ koop add provider koop-provider-pg\n\n```\n\nThis will allow you to serve your PostGIS data as GeoServices. You can add `@koopjs/output-vector-tiles` to generate `pbf` vector tiles as output:\n\n```bash\n$ koop add output @koopjs/output-vector-tiles\n```\n\n## Start the Koop server\n\n```bash\n# start koop server\n$ koop serve\n```\n\n## Vector Tiles\nThe server will now support both GeoServices and Vector Tiles as output formats to your PostGIS input data. Below is a list of the output routes for vector tiles:\n\n```bash\n# :id = ${schema}.$(table)\n\n\"VectorTileServer\" output routes for the \"pg\" provider Methods\n--------------------------------------------------------------------------- ---------\n/pg/:id/VectorTileServer/:z([0-9]+)/:x([0-9]+)/:y([0-9]+).pbf GET\n/pg/:id/VectorTileServer/tiles.json GET\n/pg/rest/services/:id/VectorTileServer/:z([0-9]+)/:x([0-9]+)/:y([0-9]+).pbf GET\n/pg/rest/services/:id/VectorTileServer GET, POST\n/pg/rest/services/:id/VectorTileServer GET, POST\n/pg/rest/services/:id/VectorTileServer/resources/styles/root.json GET\n```\n\nYour data can now be viewed in clients that support [Vector Tiles](https://github.com/koopjs/koop-output-vector-tiles). Below is an example using [ArcGIS API for Javascript](https://developers.arcgis.com/javascript/latest/) creating a [VectorTileLayer class](https://developers.arcgis.com/javascript/latest/api-reference/esri-layers-VectorTileLayer.html) with the service URL to display the data. Replace `${schema}.${table}` with schema.table from your PostGIS db to view:\n\n```javascript\n require([\"esri/Map\", \"esri/views/MapView\", \"esri/layers/VectorTileLayer\"], function(\n Map,\n MapView,\n VectorTileLayer\n ) {\n // Create a Map\n const map = new Map({\n basemap: \"dark-gray-vector\"\n });\n // Make map view and bind it to the map\n var view = new MapView({\n container: \"viewDiv\",\n map: map,\n center: [-122.31082,47.60606],\n zoom: 12\n });\n // add vector tile layer from pg-koop\n var tileLayer = new VectorTileLayer({\n url:\n \"http://localhost:8080/pg/rest/services/${schema}.${table}/VectorTileServer\"\n });\n map.add(tileLayer);\n });\n```\nGenerating the following ouput of PostGIS point data over the [Dark Gray Vector Basemap](https://www.arcgis.com/home/item.html?id=c11ce4f7801740b2905eb03ddc963ac8)\n\n\n\nYou can view the example in the [Koop PostGIS provider source](https://github.com/doneill/koop-provider-pg/blob/main/src/view/vector-tile.html).\n","slug":"koop-vector-tile","published":1,"updated":"2021-06-14T01:18:25.000Z","comments":1,"layout":"post","photos":[],"link":"","_id":"ckz0jkmd0000jnvhw4xdndfgw","content":"<p>I introduced the <a href=\"https://github.com/doneill/koop-provider-pg\">Koop PostGIS provider</a> from a developer perspective in a previous <a href=\"https://gh.jdoneill.com/2020/10/04/pgkoop/\">post</a> where the output is GeoServices. This post will guide us through installing a Koop service with support for PostGIS input and Vector Tiles as an output format.</p>\n<h2 id=\"Update-09-12-2021\"><a href=\"#Update-09-12-2021\" class=\"headerlink\" title=\"Update 09/12/2021\"></a>Update 09/12/2021</h2><p>Replaced ArcGIS Oceans Basemap with Dark Gray Vector Basemap in the client javascript example.</p>\n<h2 id=\"Install\"><a href=\"#Install\" class=\"headerlink\" title=\"Install\"></a>Install</h2><p>Koop providers require you to first install <a href=\"https://koopjs.github.io/\">Koop</a>. You can add <code>koop-provider-pg</code> to your Koop server dependencies by installing it with npm:</p>\n<figure class=\"highlight bash\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br><span class=\"line\">8</span><br><span class=\"line\">9</span><br><span class=\"line\">10</span><br><span class=\"line\">11</span><br><span class=\"line\">12</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"comment\"># install koop cli using npm</span></span><br><span class=\"line\">$ npm install -g @koopjs/cli</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"comment\"># create a new koop app</span></span><br><span class=\"line\">$ koop new app pg-koop</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"comment\"># cd into the pg-koop app</span></span><br><span class=\"line\">$ <span class=\"built_in\">cd</span> pg-koop</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"comment\"># install provider</span></span><br><span class=\"line\">$ koop add provider koop-provider-pg</span><br><span class=\"line\"></span><br></pre></td></tr></table></figure>\n\n<p>This will allow you to serve your PostGIS data as GeoServices. You can add <code>@koopjs/output-vector-tiles</code> to generate <code>pbf</code> vector tiles as output:</p>\n<figure class=\"highlight bash\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">$ koop add output @koopjs/output-vector-tiles</span><br></pre></td></tr></table></figure>\n\n<h2 id=\"Start-the-Koop-server\"><a href=\"#Start-the-Koop-server\" class=\"headerlink\" title=\"Start the Koop server\"></a>Start the Koop server</h2><figure class=\"highlight bash\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"comment\"># start koop server</span></span><br><span class=\"line\">$ koop serve</span><br></pre></td></tr></table></figure>\n\n<h2 id=\"Vector-Tiles\"><a href=\"#Vector-Tiles\" class=\"headerlink\" title=\"Vector Tiles\"></a>Vector Tiles</h2><p>The server will now support both GeoServices and Vector Tiles as output formats to your PostGIS input data. Below is a list of the output routes for vector tiles:</p>\n<figure class=\"highlight bash\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br><span class=\"line\">8</span><br><span class=\"line\">9</span><br><span class=\"line\">10</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"comment\"># :id = ${schema}.$(table)</span></span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"string\">"VectorTileServer"</span> output routes <span class=\"keyword\">for</span> the <span class=\"string\">"pg"</span> provider Methods</span><br><span class=\"line\">--------------------------------------------------------------------------- ---------</span><br><span class=\"line\">/pg/:id/VectorTileServer/:z([0-9]+)/:x([0-9]+)/:y([0-9]+).pbf GET</span><br><span class=\"line\">/pg/:id/VectorTileServer/tiles.json GET</span><br><span class=\"line\">/pg/rest/services/:id/VectorTileServer/:z([0-9]+)/:x([0-9]+)/:y([0-9]+).pbf GET</span><br><span class=\"line\">/pg/rest/services/:id/VectorTileServer GET, POST</span><br><span class=\"line\">/pg/rest/services/:id/VectorTileServer GET, POST</span><br><span class=\"line\">/pg/rest/services/:id/VectorTileServer/resources/styles/root.json GET</span><br></pre></td></tr></table></figure>\n\n<p>Your data can now be viewed in clients that support <a href=\"https://github.com/koopjs/koop-output-vector-tiles\">Vector Tiles</a>. Below is an example using <a href=\"https://developers.arcgis.com/javascript/latest/\">ArcGIS API for Javascript</a> creating a <a href=\"https://developers.arcgis.com/javascript/latest/api-reference/esri-layers-VectorTileLayer.html\">VectorTileLayer class</a> with the service URL to display the data. Replace <code>${schema}.${table}</code> with schema.table from your PostGIS db to view:</p>\n<figure class=\"highlight javascript\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br><span class=\"line\">8</span><br><span class=\"line\">9</span><br><span class=\"line\">10</span><br><span class=\"line\">11</span><br><span class=\"line\">12</span><br><span class=\"line\">13</span><br><span class=\"line\">14</span><br><span class=\"line\">15</span><br><span class=\"line\">16</span><br><span class=\"line\">17</span><br><span class=\"line\">18</span><br><span class=\"line\">19</span><br><span class=\"line\">20</span><br><span class=\"line\">21</span><br><span class=\"line\">22</span><br><span class=\"line\">23</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"built_in\">require</span>([<span class=\"string\">"esri/Map"</span>, <span class=\"string\">"esri/views/MapView"</span>, <span class=\"string\">"esri/layers/VectorTileLayer"</span>], <span class=\"function\"><span class=\"keyword\">function</span>(<span class=\"params\"></span></span></span><br><span class=\"line\"><span class=\"params\"><span class=\"function\"> <span class=\"built_in\">Map</span>,</span></span></span><br><span class=\"line\"><span class=\"params\"><span class=\"function\"> MapView,</span></span></span><br><span class=\"line\"><span class=\"params\"><span class=\"function\"> VectorTileLayer</span></span></span><br><span class=\"line\"><span class=\"params\"><span class=\"function\"></span>) </span>{</span><br><span class=\"line\"> <span class=\"comment\">// Create a Map</span></span><br><span class=\"line\"> <span class=\"keyword\">const</span> map = <span class=\"keyword\">new</span> <span class=\"built_in\">Map</span>({</span><br><span class=\"line\"> <span class=\"attr\">basemap</span>: <span class=\"string\">"dark-gray-vector"</span></span><br><span class=\"line\"> });</span><br><span class=\"line\"> <span class=\"comment\">// Make map view and bind it to the map</span></span><br><span class=\"line\"> <span class=\"keyword\">var</span> view = <span class=\"keyword\">new</span> MapView({</span><br><span class=\"line\"> <span class=\"attr\">container</span>: <span class=\"string\">"viewDiv"</span>,</span><br><span class=\"line\"> <span class=\"attr\">map</span>: map,</span><br><span class=\"line\"> <span class=\"attr\">center</span>: [-<span class=\"number\">122.31082</span>,<span class=\"number\">47.60606</span>],</span><br><span class=\"line\"> <span class=\"attr\">zoom</span>: <span class=\"number\">12</span></span><br><span class=\"line\"> });</span><br><span class=\"line\"> <span class=\"comment\">// add vector tile layer from pg-koop</span></span><br><span class=\"line\"> <span class=\"keyword\">var</span> tileLayer = <span class=\"keyword\">new</span> VectorTileLayer({</span><br><span class=\"line\"> <span class=\"attr\">url</span>:</span><br><span class=\"line\"> <span class=\"string\">"http://localhost:8080/pg/rest/services/${schema}.${table}/VectorTileServer"</span></span><br><span class=\"line\"> });</span><br><span class=\"line\"> map.add(tileLayer);</span><br><span class=\"line\">});</span><br></pre></td></tr></table></figure>\n<p>Generating the following ouput of PostGIS point data over the <a href=\"https://www.arcgis.com/home/item.html?id=c11ce4f7801740b2905eb03ddc963ac8\">Dark Gray Vector Basemap</a></p>\n<p><img src=\"/2021/06/13/koop-vector-tile/wa-boat-launch.png\" alt=\"Esri JS\" title=\"PostGIS Vector Tiles in Esri JS\"></p>\n<p>You can view the example in the <a href=\"https://github.com/doneill/koop-provider-pg/blob/main/src/view/vector-tile.html\">Koop PostGIS provider source</a>.</p>\n","site":{"data":{"projects":[{"name":"koop-provider-pg","url":"https://www.npmjs.com/package/koop-provider-pg","desc":"Koop provider to query PostGIS spatial data"},{"name":"Map Preview","url":"https://packagecontrol.io/packages/MapPreview","desc":"Preview map layer files in your web browser using Sublime Text 3"},{"name":"kotlin mulitplatform template","url":"https://github.com/doneill/kotlin-multiplatform-template","desc":"Kotlin MPP template to bootstrap Mobile/Web"}]}},"excerpt":"","more":"<p>I introduced the <a href=\"https://github.com/doneill/koop-provider-pg\">Koop PostGIS provider</a> from a developer perspective in a previous <a href=\"https://gh.jdoneill.com/2020/10/04/pgkoop/\">post</a> where the output is GeoServices. This post will guide us through installing a Koop service with support for PostGIS input and Vector Tiles as an output format.</p>\n<h2 id=\"Update-09-12-2021\"><a href=\"#Update-09-12-2021\" class=\"headerlink\" title=\"Update 09/12/2021\"></a>Update 09/12/2021</h2><p>Replaced ArcGIS Oceans Basemap with Dark Gray Vector Basemap in the client javascript example.</p>\n<h2 id=\"Install\"><a href=\"#Install\" class=\"headerlink\" title=\"Install\"></a>Install</h2><p>Koop providers require you to first install <a href=\"https://koopjs.github.io/\">Koop</a>. You can add <code>koop-provider-pg</code> to your Koop server dependencies by installing it with npm:</p>\n<figure class=\"highlight bash\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br><span class=\"line\">8</span><br><span class=\"line\">9</span><br><span class=\"line\">10</span><br><span class=\"line\">11</span><br><span class=\"line\">12</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"comment\"># install koop cli using npm</span></span><br><span class=\"line\">$ npm install -g @koopjs/cli</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"comment\"># create a new koop app</span></span><br><span class=\"line\">$ koop new app pg-koop</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"comment\"># cd into the pg-koop app</span></span><br><span class=\"line\">$ <span class=\"built_in\">cd</span> pg-koop</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"comment\"># install provider</span></span><br><span class=\"line\">$ koop add provider koop-provider-pg</span><br><span class=\"line\"></span><br></pre></td></tr></table></figure>\n\n<p>This will allow you to serve your PostGIS data as GeoServices. You can add <code>@koopjs/output-vector-tiles</code> to generate <code>pbf</code> vector tiles as output:</p>\n<figure class=\"highlight bash\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">$ koop add output @koopjs/output-vector-tiles</span><br></pre></td></tr></table></figure>\n\n<h2 id=\"Start-the-Koop-server\"><a href=\"#Start-the-Koop-server\" class=\"headerlink\" title=\"Start the Koop server\"></a>Start the Koop server</h2><figure class=\"highlight bash\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"comment\"># start koop server</span></span><br><span class=\"line\">$ koop serve</span><br></pre></td></tr></table></figure>\n\n<h2 id=\"Vector-Tiles\"><a href=\"#Vector-Tiles\" class=\"headerlink\" title=\"Vector Tiles\"></a>Vector Tiles</h2><p>The server will now support both GeoServices and Vector Tiles as output formats to your PostGIS input data. Below is a list of the output routes for vector tiles:</p>\n<figure class=\"highlight bash\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br><span class=\"line\">8</span><br><span class=\"line\">9</span><br><span class=\"line\">10</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"comment\"># :id = ${schema}.$(table)</span></span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"string\">"VectorTileServer"</span> output routes <span class=\"keyword\">for</span> the <span class=\"string\">"pg"</span> provider Methods</span><br><span class=\"line\">--------------------------------------------------------------------------- ---------</span><br><span class=\"line\">/pg/:id/VectorTileServer/:z([0-9]+)/:x([0-9]+)/:y([0-9]+).pbf GET</span><br><span class=\"line\">/pg/:id/VectorTileServer/tiles.json GET</span><br><span class=\"line\">/pg/rest/services/:id/VectorTileServer/:z([0-9]+)/:x([0-9]+)/:y([0-9]+).pbf GET</span><br><span class=\"line\">/pg/rest/services/:id/VectorTileServer GET, POST</span><br><span class=\"line\">/pg/rest/services/:id/VectorTileServer GET, POST</span><br><span class=\"line\">/pg/rest/services/:id/VectorTileServer/resources/styles/root.json GET</span><br></pre></td></tr></table></figure>\n\n<p>Your data can now be viewed in clients that support <a href=\"https://github.com/koopjs/koop-output-vector-tiles\">Vector Tiles</a>. Below is an example using <a href=\"https://developers.arcgis.com/javascript/latest/\">ArcGIS API for Javascript</a> creating a <a href=\"https://developers.arcgis.com/javascript/latest/api-reference/esri-layers-VectorTileLayer.html\">VectorTileLayer class</a> with the service URL to display the data. Replace <code>${schema}.${table}</code> with schema.table from your PostGIS db to view:</p>\n<figure class=\"highlight javascript\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br><span class=\"line\">8</span><br><span class=\"line\">9</span><br><span class=\"line\">10</span><br><span class=\"line\">11</span><br><span class=\"line\">12</span><br><span class=\"line\">13</span><br><span class=\"line\">14</span><br><span class=\"line\">15</span><br><span class=\"line\">16</span><br><span class=\"line\">17</span><br><span class=\"line\">18</span><br><span class=\"line\">19</span><br><span class=\"line\">20</span><br><span class=\"line\">21</span><br><span class=\"line\">22</span><br><span class=\"line\">23</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"built_in\">require</span>([<span class=\"string\">"esri/Map"</span>, <span class=\"string\">"esri/views/MapView"</span>, <span class=\"string\">"esri/layers/VectorTileLayer"</span>], <span class=\"function\"><span class=\"keyword\">function</span>(<span class=\"params\"></span></span></span><br><span class=\"line\"><span class=\"params\"><span class=\"function\"> <span class=\"built_in\">Map</span>,</span></span></span><br><span class=\"line\"><span class=\"params\"><span class=\"function\"> MapView,</span></span></span><br><span class=\"line\"><span class=\"params\"><span class=\"function\"> VectorTileLayer</span></span></span><br><span class=\"line\"><span class=\"params\"><span class=\"function\"></span>) </span>{</span><br><span class=\"line\"> <span class=\"comment\">// Create a Map</span></span><br><span class=\"line\"> <span class=\"keyword\">const</span> map = <span class=\"keyword\">new</span> <span class=\"built_in\">Map</span>({</span><br><span class=\"line\"> <span class=\"attr\">basemap</span>: <span class=\"string\">"dark-gray-vector"</span></span><br><span class=\"line\"> });</span><br><span class=\"line\"> <span class=\"comment\">// Make map view and bind it to the map</span></span><br><span class=\"line\"> <span class=\"keyword\">var</span> view = <span class=\"keyword\">new</span> MapView({</span><br><span class=\"line\"> <span class=\"attr\">container</span>: <span class=\"string\">"viewDiv"</span>,</span><br><span class=\"line\"> <span class=\"attr\">map</span>: map,</span><br><span class=\"line\"> <span class=\"attr\">center</span>: [-<span class=\"number\">122.31082</span>,<span class=\"number\">47.60606</span>],</span><br><span class=\"line\"> <span class=\"attr\">zoom</span>: <span class=\"number\">12</span></span><br><span class=\"line\"> });</span><br><span class=\"line\"> <span class=\"comment\">// add vector tile layer from pg-koop</span></span><br><span class=\"line\"> <span class=\"keyword\">var</span> tileLayer = <span class=\"keyword\">new</span> VectorTileLayer({</span><br><span class=\"line\"> <span class=\"attr\">url</span>:</span><br><span class=\"line\"> <span class=\"string\">"http://localhost:8080/pg/rest/services/${schema}.${table}/VectorTileServer"</span></span><br><span class=\"line\"> });</span><br><span class=\"line\"> map.add(tileLayer);</span><br><span class=\"line\">});</span><br></pre></td></tr></table></figure>\n<p>Generating the following ouput of PostGIS point data over the <a href=\"https://www.arcgis.com/home/item.html?id=c11ce4f7801740b2905eb03ddc963ac8\">Dark Gray Vector Basemap</a></p>\n<p><img src=\"/2021/06/13/koop-vector-tile/wa-boat-launch.png\" alt=\"Esri JS\" title=\"PostGIS Vector Tiles in Esri JS\"></p>\n<p>You can view the example in the <a href=\"https://github.com/doneill/koop-provider-pg/blob/main/src/view/vector-tile.html\">Koop PostGIS provider source</a>.</p>\n"},{"title":"Kotlin CLI","date":"2021-06-20T01:18:25.000Z","_content":"\nAutomating things via command line tools is awesome and after doing some experimenting with Kotlin as a modern programming language for cli applications and inspired by the [kotlin cli starter repo](https://github.com/jmfayard/kotlin-cli-starter) I wanted to break down creating the cli application from scratch using [CLIKT](https://ajalt.github.io/clikt/) and [Coroutines](https://kotlinlang.org/docs/coroutines-overview.html).\n\n## Create a single module Gradle project\nCreate a project folder and skaffold it a Gradle application project:\n\n```bash\n# create your poject directory\n$ mkdir demo\n\n# initialize it with Gradle project structure\n$ cd demo && gradle init\n```\n\nChoose the following project settings when prompted:\n\n```bash\n# select 2 application project\nSelect type of project to generate:\n 1: basic\n 2: application\n 3: library\n 4: Gradle plugin\nEnter selection (default: basic) [1..4] 2\n\n# Kotlin implementation language\nSelect implementation language:\n 1: C++\n 2: Groovy\n 3: Java\n 4: Kotlin\n 5: Scala\n 6: Swift\nEnter selection (default: Java) [1..6] 4\n\n# simple on application project\nSplit functionality across multiple subprojects?:\n 1: no - only one application project\n 2: yes - application and library projects\nEnter selection (default: no - only one application project) [1..2] 1\n\n# kotlin dsl\nSelect build script DSL:\n 1: Groovy\n 2: Kotlin\nEnter selection (default: Kotlin) [1..2] 2\n```\n\nNow we have an empty application project. Let's clean it up a bit. The application structure created a single module project, we can remove the module and use the root projct as the module source:\n\n```bash\n# move the build script to root project\n$ mv app/build.gradle.kts build.gradle.kts\n# remove app module, use project root as module source\n$ mv app/src src && rm -rf app\n```\n\nOpen `settings.gradle.kts` and remove the module reference `include(\"app\")`. Your app directory structure should look simiilar to the following:\n\n<p align=\"rigth\">\n <img align=\"left\" width=\"300\" height=\"244\" src=\"app-structure.png\">\n</p>\n\n## Dependencies\nA common way to manage gradle dependencies is to have an `ext{}` block in your root project. This doesn't provide any code completion or code navigation, in order to suppport those features we will define dependencies as String constants in `Dependencies.kt` inside of `buildSrc` folder. The directory `buildSrc` is treated as an included build where Gradle automatically compiles its sources.\n\n```bash\n# make a buildSrc directory and source package structure\n$ mkdir -p buildSrc/{src/{main/{kotlin,},},}\n# create Dependencies.kt\n$ touch buildSrc/src/main/kotlin/Dependencies.kt\n```\n\nNow we need to create a build script for our Dependencies\n\n```bash\n$ touch buildSrc/build.gradle.kts\n```\n\nOpen the new `build.gradle.kts` file and add the following:\n\n```kotlin\nplugins {\n `kotlin-dsl`\n}\n\nrepositories {\n mavenCentral()\n}\n```\n\n### Asynchronous Command Line Iterface for Kotlin\nWe will use `CLIKT` multiplatform library which makes writing cli apps easy and `Coroutines` for an asynchronous, or non-blocking, application. Open `buildSrc/src/main/kotlin/Dependencies.kt` file to add the following dependencies and versions:\n\n```kotlin\nobject Versions {\n const val CLIKT = \"3.2.0\"\n const val COROUTINES = \"1.5.0\"\n const val JVM = \"1.8\"\n const val KOTLIN = \"1.4.32\"\n}\n\nobject Clikt {\n const val CLIKT = \"com.github.ajalt.clikt:clikt:${Versions.CLIKT}\"\n}\n\nobject Coroutines {\n const val CORE = \"org.jetbrains.kotlinx:kotlinx-coroutines-core:${Versions.COROUTINES}\"\n}\n```\n\nNow we can access these in our applications build script. Open the root `build.gradle.kts` file to add the dependencies and do some version updates:\n\n```kotlin\n// add to the dependencies block\ndependencies {\n // clikt\n implementation(Clikt.CLIKT)\n // coroutines\n implementation(Coroutines.CORE)\n}\n```\n\nOptionally, we can update our kotlin version\n\n```kotlin\n// update plugins block\nplugins {\n application\n kotlin(\"jvm\") version Versions.KOTLIN\n}\n```\n\nOnce you start using `buildSrc` for dependencies you can update many more build script definitions.\n\n## Demo CLI App\nNow that our project structure is complete we can start build our cli application. We will reate a `cli` package for our source code and create the following classes/objects:\n\n- Main.kt: Our application entry\n- Demo.kt: Our suspendable computation\n- DemoConfig.kt: Our applications configurations\n- DemoCommand.kt: Our applications teminal commands\n\n```bash\n# create package directory\n$ mkdir src/main/kotlin/cli && touch src/main/kotlin/cli/Main.kt src/main/kotlin/cli/Demo.kt src/main/kotlin/cli/DemoConfig.kt src/main/kotlin/cli/DemoCommand.kt\n```\n\nThe `DemoConfig.kt` is a utility to house some globals, for now we will use to to reference our app command name. Open `DemoConfig.kt` to and add the following:\n\n```kotlin\npackage cli\n\nobject DemoConfig {\n val COMMAND_NAME = \"demo\"\n}\n```\n\nThe `DemoCommand.kt` is a subclass of [CliktCommand](https://ajalt.github.io/clikt/api/clikt/com.github.ajalt.clikt.core/-clikt-command/). We will set it up with our help page and support for verbose output.\n\n```kotlin\nclass DemoCommand: CliktCommand(\n help = \"\"\"\n Demo API CLI.\n \"\"\".trimIndent(),\n epilog = \"\"\"\n Try it with a demo type\n\n Examples:\n ${COMMAND_NAME} hello\n \"\"\".trimIndent(),\n name = COMMAND_NAME\n) {\n init {\n completionOption()\n }\n\n val help: Boolean by option(\"-h\", \"--help\", help = \"Display this help screen\").flag()\n val verbose by option(\"-v\", \"--verbose\", help = \"verbose\").flag(defaultForHelp = \"disabled\")\n\n override fun run() {\n if (verbose) println(this)\n }\n}\n```\n\nThe `Demo.kt` will have our asynchronous methods to do work, to start we look `help` in the `args` passed to the application.\n\n```kotlin\npackage cli\n\nsuspend fun runDemo(args: Array<String>) {\n val command = DemoCommand()\n\n command.main(args)\n\n if (command.help) {\n println(command.getFormattedHelp())\n return\n }\n}\n```\n\nThe `Main.kt` will be our application entry point. We will run the app in a new coroutine that blocks the current thread until its completion. `runBlocking` is designed to bridge blocking code to suspending `runDemo` function.\n\n```kotlin\npackage cli\n\nimport kotlinx.coroutines.runBlocking\n\nfun main(args: Array<String>) {\n runBlocking { runDemo(args) }\n}\n```\n\nWith our source code ready we need to define our applications entry point. We can do this by defining our application entry class. Following our `buildSrc` pattern we can create a new class to source our application configuration:\n\n```bash\n# create Coordinates.kt in buildSrc\n$ touch buildSrc/src/main/kotlin/Coordinates.kt\n```\n\nOpen the newly created `Coordinates.kt` file and add some application configruateions:\n\n```kotlin\nobject AppCoordinates {\n const val APP_ID = \"cli.MainKt\"\n\n const val APP_VERSION_NAME = \"0.0.1\"\n const val APP_VERSION_CODE = 1\n}\n```\n\nNow we can reference the `APP_ID` as the main class for our application in the root `build.gradle.kts` build script, open it and add the following:\n\n```kotlin\napplication {\n mainClass.set(AppCoordinates.APP_ID)\n}\n```\n\n## Run the app\nAt this point the application can be assembled and distributed.\n\n```bash\n# build the app\n$ ./gradlew assemble\n# build a distribution\n$ ./gradlew installDist\n```\n\nThis will generate an executable in `build/install/demo/bin/demo`. We can create a run script at the root of the project to expedite and run the application from project root.\n\n```bash\n# create a run script\n$ touch demo && chmod 755 demo\n```\n\nOpen `demo` and add the following:\n\n```bash\n#!/usr/bin/env bash\n\n./gradlew --quiet \"installDist\" && \"build/install/demo/bin/demo\" \"$@\"\n```\n\nNow we can run `./demo --help` to see our help page defined in `DemoCommand.kt`\n\n```bash\nUsage: demo [OPTIONS]\n\n Demo API CLI.\n\nOptions:\n --generate-completion [bash|zsh|fish]\n -h, --help Display this help screen\n -v, --verbose verbose\n\nTry it with a demo type\n\nExamples: Demo hello\n```\n\nAnd with that we have successfully skaffold a Kotlin cli application using `Clikt` and `Coroutines`, happy coding!","source":"_posts/kotlin-cli.md","raw":"---\ntitle: Kotlin CLI\ndate: 2021-06-19 18:18:25\ntags:\n- kotlin\n- clikt\n- coroutines\n---\n\nAutomating things via command line tools is awesome and after doing some experimenting with Kotlin as a modern programming language for cli applications and inspired by the [kotlin cli starter repo](https://github.com/jmfayard/kotlin-cli-starter) I wanted to break down creating the cli application from scratch using [CLIKT](https://ajalt.github.io/clikt/) and [Coroutines](https://kotlinlang.org/docs/coroutines-overview.html).\n\n## Create a single module Gradle project\nCreate a project folder and skaffold it a Gradle application project:\n\n```bash\n# create your poject directory\n$ mkdir demo\n\n# initialize it with Gradle project structure\n$ cd demo && gradle init\n```\n\nChoose the following project settings when prompted:\n\n```bash\n# select 2 application project\nSelect type of project to generate:\n 1: basic\n 2: application\n 3: library\n 4: Gradle plugin\nEnter selection (default: basic) [1..4] 2\n\n# Kotlin implementation language\nSelect implementation language:\n 1: C++\n 2: Groovy\n 3: Java\n 4: Kotlin\n 5: Scala\n 6: Swift\nEnter selection (default: Java) [1..6] 4\n\n# simple on application project\nSplit functionality across multiple subprojects?:\n 1: no - only one application project\n 2: yes - application and library projects\nEnter selection (default: no - only one application project) [1..2] 1\n\n# kotlin dsl\nSelect build script DSL:\n 1: Groovy\n 2: Kotlin\nEnter selection (default: Kotlin) [1..2] 2\n```\n\nNow we have an empty application project. Let's clean it up a bit. The application structure created a single module project, we can remove the module and use the root projct as the module source:\n\n```bash\n# move the build script to root project\n$ mv app/build.gradle.kts build.gradle.kts\n# remove app module, use project root as module source\n$ mv app/src src && rm -rf app\n```\n\nOpen `settings.gradle.kts` and remove the module reference `include(\"app\")`. Your app directory structure should look simiilar to the following:\n\n<p align=\"rigth\">\n <img align=\"left\" width=\"300\" height=\"244\" src=\"app-structure.png\">\n</p>\n\n## Dependencies\nA common way to manage gradle dependencies is to have an `ext{}` block in your root project. This doesn't provide any code completion or code navigation, in order to suppport those features we will define dependencies as String constants in `Dependencies.kt` inside of `buildSrc` folder. The directory `buildSrc` is treated as an included build where Gradle automatically compiles its sources.\n\n```bash\n# make a buildSrc directory and source package structure\n$ mkdir -p buildSrc/{src/{main/{kotlin,},},}\n# create Dependencies.kt\n$ touch buildSrc/src/main/kotlin/Dependencies.kt\n```\n\nNow we need to create a build script for our Dependencies\n\n```bash\n$ touch buildSrc/build.gradle.kts\n```\n\nOpen the new `build.gradle.kts` file and add the following:\n\n```kotlin\nplugins {\n `kotlin-dsl`\n}\n\nrepositories {\n mavenCentral()\n}\n```\n\n### Asynchronous Command Line Iterface for Kotlin\nWe will use `CLIKT` multiplatform library which makes writing cli apps easy and `Coroutines` for an asynchronous, or non-blocking, application. Open `buildSrc/src/main/kotlin/Dependencies.kt` file to add the following dependencies and versions:\n\n```kotlin\nobject Versions {\n const val CLIKT = \"3.2.0\"\n const val COROUTINES = \"1.5.0\"\n const val JVM = \"1.8\"\n const val KOTLIN = \"1.4.32\"\n}\n\nobject Clikt {\n const val CLIKT = \"com.github.ajalt.clikt:clikt:${Versions.CLIKT}\"\n}\n\nobject Coroutines {\n const val CORE = \"org.jetbrains.kotlinx:kotlinx-coroutines-core:${Versions.COROUTINES}\"\n}\n```\n\nNow we can access these in our applications build script. Open the root `build.gradle.kts` file to add the dependencies and do some version updates:\n\n```kotlin\n// add to the dependencies block\ndependencies {\n // clikt\n implementation(Clikt.CLIKT)\n // coroutines\n implementation(Coroutines.CORE)\n}\n```\n\nOptionally, we can update our kotlin version\n\n```kotlin\n// update plugins block\nplugins {\n application\n kotlin(\"jvm\") version Versions.KOTLIN\n}\n```\n\nOnce you start using `buildSrc` for dependencies you can update many more build script definitions.\n\n## Demo CLI App\nNow that our project structure is complete we can start build our cli application. We will reate a `cli` package for our source code and create the following classes/objects:\n\n- Main.kt: Our application entry\n- Demo.kt: Our suspendable computation\n- DemoConfig.kt: Our applications configurations\n- DemoCommand.kt: Our applications teminal commands\n\n```bash\n# create package directory\n$ mkdir src/main/kotlin/cli && touch src/main/kotlin/cli/Main.kt src/main/kotlin/cli/Demo.kt src/main/kotlin/cli/DemoConfig.kt src/main/kotlin/cli/DemoCommand.kt\n```\n\nThe `DemoConfig.kt` is a utility to house some globals, for now we will use to to reference our app command name. Open `DemoConfig.kt` to and add the following:\n\n```kotlin\npackage cli\n\nobject DemoConfig {\n val COMMAND_NAME = \"demo\"\n}\n```\n\nThe `DemoCommand.kt` is a subclass of [CliktCommand](https://ajalt.github.io/clikt/api/clikt/com.github.ajalt.clikt.core/-clikt-command/). We will set it up with our help page and support for verbose output.\n\n```kotlin\nclass DemoCommand: CliktCommand(\n help = \"\"\"\n Demo API CLI.\n \"\"\".trimIndent(),\n epilog = \"\"\"\n Try it with a demo type\n\n Examples:\n ${COMMAND_NAME} hello\n \"\"\".trimIndent(),\n name = COMMAND_NAME\n) {\n init {\n completionOption()\n }\n\n val help: Boolean by option(\"-h\", \"--help\", help = \"Display this help screen\").flag()\n val verbose by option(\"-v\", \"--verbose\", help = \"verbose\").flag(defaultForHelp = \"disabled\")\n\n override fun run() {\n if (verbose) println(this)\n }\n}\n```\n\nThe `Demo.kt` will have our asynchronous methods to do work, to start we look `help` in the `args` passed to the application.\n\n```kotlin\npackage cli\n\nsuspend fun runDemo(args: Array<String>) {\n val command = DemoCommand()\n\n command.main(args)\n\n if (command.help) {\n println(command.getFormattedHelp())\n return\n }\n}\n```\n\nThe `Main.kt` will be our application entry point. We will run the app in a new coroutine that blocks the current thread until its completion. `runBlocking` is designed to bridge blocking code to suspending `runDemo` function.\n\n```kotlin\npackage cli\n\nimport kotlinx.coroutines.runBlocking\n\nfun main(args: Array<String>) {\n runBlocking { runDemo(args) }\n}\n```\n\nWith our source code ready we need to define our applications entry point. We can do this by defining our application entry class. Following our `buildSrc` pattern we can create a new class to source our application configuration:\n\n```bash\n# create Coordinates.kt in buildSrc\n$ touch buildSrc/src/main/kotlin/Coordinates.kt\n```\n\nOpen the newly created `Coordinates.kt` file and add some application configruateions:\n\n```kotlin\nobject AppCoordinates {\n const val APP_ID = \"cli.MainKt\"\n\n const val APP_VERSION_NAME = \"0.0.1\"\n const val APP_VERSION_CODE = 1\n}\n```\n\nNow we can reference the `APP_ID` as the main class for our application in the root `build.gradle.kts` build script, open it and add the following:\n\n```kotlin\napplication {\n mainClass.set(AppCoordinates.APP_ID)\n}\n```\n\n## Run the app\nAt this point the application can be assembled and distributed.\n\n```bash\n# build the app\n$ ./gradlew assemble\n# build a distribution\n$ ./gradlew installDist\n```\n\nThis will generate an executable in `build/install/demo/bin/demo`. We can create a run script at the root of the project to expedite and run the application from project root.\n\n```bash\n# create a run script\n$ touch demo && chmod 755 demo\n```\n\nOpen `demo` and add the following:\n\n```bash\n#!/usr/bin/env bash\n\n./gradlew --quiet \"installDist\" && \"build/install/demo/bin/demo\" \"$@\"\n```\n\nNow we can run `./demo --help` to see our help page defined in `DemoCommand.kt`\n\n```bash\nUsage: demo [OPTIONS]\n\n Demo API CLI.\n\nOptions:\n --generate-completion [bash|zsh|fish]\n -h, --help Display this help screen\n -v, --verbose verbose\n\nTry it with a demo type\n\nExamples: Demo hello\n```\n\nAnd with that we have successfully skaffold a Kotlin cli application using `Clikt` and `Coroutines`, happy coding!","slug":"kotlin-cli","published":1,"updated":"2021-06-20T01:18:25.000Z","comments":1,"layout":"post","photos":[],"link":"","_id":"ckz0jkmd1000knvhw820s48gp","content":"<p>Automating things via command line tools is awesome and after doing some experimenting with Kotlin as a modern programming language for cli applications and inspired by the <a href=\"https://github.com/jmfayard/kotlin-cli-starter\">kotlin cli starter repo</a> I wanted to break down creating the cli application from scratch using <a href=\"https://ajalt.github.io/clikt/\">CLIKT</a> and <a href=\"https://kotlinlang.org/docs/coroutines-overview.html\">Coroutines</a>.</p>\n<h2 id=\"Create-a-single-module-Gradle-project\"><a href=\"#Create-a-single-module-Gradle-project\" class=\"headerlink\" title=\"Create a single module Gradle project\"></a>Create a single module Gradle project</h2><p>Create a project folder and skaffold it a Gradle application project:</p>\n<figure class=\"highlight bash\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"comment\"># create your poject directory</span></span><br><span class=\"line\">$ mkdir demo</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"comment\"># initialize it with Gradle project structure</span></span><br><span class=\"line\">$ <span class=\"built_in\">cd</span> demo && gradle init</span><br></pre></td></tr></table></figure>\n\n<p>Choose the following project settings when prompted:</p>\n<figure class=\"highlight bash\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br><span class=\"line\">8</span><br><span class=\"line\">9</span><br><span class=\"line\">10</span><br><span class=\"line\">11</span><br><span class=\"line\">12</span><br><span class=\"line\">13</span><br><span class=\"line\">14</span><br><span class=\"line\">15</span><br><span class=\"line\">16</span><br><span class=\"line\">17</span><br><span class=\"line\">18</span><br><span class=\"line\">19</span><br><span class=\"line\">20</span><br><span class=\"line\">21</span><br><span class=\"line\">22</span><br><span class=\"line\">23</span><br><span class=\"line\">24</span><br><span class=\"line\">25</span><br><span class=\"line\">26</span><br><span class=\"line\">27</span><br><span class=\"line\">28</span><br><span class=\"line\">29</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"comment\"># select 2 application project</span></span><br><span class=\"line\">Select <span class=\"built_in\">type</span> of project to generate:</span><br><span class=\"line\"> 1: basic</span><br><span class=\"line\"> 2: application</span><br><span class=\"line\"> 3: library</span><br><span class=\"line\"> 4: Gradle plugin</span><br><span class=\"line\">Enter selection (default: basic) [1..4] 2</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"comment\"># Kotlin implementation language</span></span><br><span class=\"line\">Select implementation language:</span><br><span class=\"line\"> 1: C++</span><br><span class=\"line\"> 2: Groovy</span><br><span class=\"line\"> 3: Java</span><br><span class=\"line\"> 4: Kotlin</span><br><span class=\"line\"> 5: Scala</span><br><span class=\"line\"> 6: Swift</span><br><span class=\"line\">Enter selection (default: Java) [1..6] 4</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"comment\"># simple on application project</span></span><br><span class=\"line\">Split functionality across multiple subprojects?:</span><br><span class=\"line\"> 1: no - only one application project</span><br><span class=\"line\"> 2: yes - application and library projects</span><br><span class=\"line\">Enter selection (default: no - only one application project) [1..2] 1</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"comment\"># kotlin dsl</span></span><br><span class=\"line\">Select build script DSL:</span><br><span class=\"line\"> 1: Groovy</span><br><span class=\"line\"> 2: Kotlin</span><br><span class=\"line\">Enter selection (default: Kotlin) [1..2] 2</span><br></pre></td></tr></table></figure>\n\n<p>Now we have an empty application project. Let’s clean it up a bit. The application structure created a single module project, we can remove the module and use the root projct as the module source:</p>\n<figure class=\"highlight bash\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"comment\"># move the build script to root project</span></span><br><span class=\"line\">$ mv app/build.gradle.kts build.gradle.kts</span><br><span class=\"line\"><span class=\"comment\"># remove app module, use project root as module source</span></span><br><span class=\"line\">$ mv app/src src && rm -rf app</span><br></pre></td></tr></table></figure>\n\n<p>Open <code>settings.gradle.kts</code> and remove the module reference <code>include("app")</code>. Your app directory structure should look simiilar to the following:</p>\n<p align=\"rigth\">\n <img align=\"left\" width=\"300\" height=\"244\" src=\"app-structure.png\">\n</p>\n\n<h2 id=\"Dependencies\"><a href=\"#Dependencies\" class=\"headerlink\" title=\"Dependencies\"></a>Dependencies</h2><p>A common way to manage gradle dependencies is to have an <code>ext{}</code> block in your root project. This doesn’t provide any code completion or code navigation, in order to suppport those features we will define dependencies as String constants in <code>Dependencies.kt</code> inside of <code>buildSrc</code> folder. The directory <code>buildSrc</code> is treated as an included build where Gradle automatically compiles its sources.</p>\n<figure class=\"highlight bash\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"comment\"># make a buildSrc directory and source package structure</span></span><br><span class=\"line\">$ mkdir -p buildSrc/{src/{main/{kotlin,},},}</span><br><span class=\"line\"><span class=\"comment\"># create Dependencies.kt</span></span><br><span class=\"line\">$ touch buildSrc/src/main/kotlin/Dependencies.kt</span><br></pre></td></tr></table></figure>\n\n<p>Now we need to create a build script for our Dependencies</p>\n<figure class=\"highlight bash\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">$ touch buildSrc/build.gradle.kts</span><br></pre></td></tr></table></figure>\n\n<p>Open the new <code>build.gradle.kts</code> file and add the following:</p>\n<figure class=\"highlight kotlin\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">plugins {</span><br><span class=\"line\"> `kotlin-dsl`</span><br><span class=\"line\">}</span><br><span class=\"line\"></span><br><span class=\"line\">repositories {</span><br><span class=\"line\"> mavenCentral()</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n\n<h3 id=\"Asynchronous-Command-Line-Iterface-for-Kotlin\"><a href=\"#Asynchronous-Command-Line-Iterface-for-Kotlin\" class=\"headerlink\" title=\"Asynchronous Command Line Iterface for Kotlin\"></a>Asynchronous Command Line Iterface for Kotlin</h3><p>We will use <code>CLIKT</code> multiplatform library which makes writing cli apps easy and <code>Coroutines</code> for an asynchronous, or non-blocking, application. Open <code>buildSrc/src/main/kotlin/Dependencies.kt</code> file to add the following dependencies and versions:</p>\n<figure class=\"highlight kotlin\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br><span class=\"line\">8</span><br><span class=\"line\">9</span><br><span class=\"line\">10</span><br><span class=\"line\">11</span><br><span class=\"line\">12</span><br><span class=\"line\">13</span><br><span class=\"line\">14</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">object</span> Versions {</span><br><span class=\"line\"> <span class=\"keyword\">const</span> <span class=\"keyword\">val</span> CLIKT = <span class=\"string\">"3.2.0"</span></span><br><span class=\"line\"> <span class=\"keyword\">const</span> <span class=\"keyword\">val</span> COROUTINES = <span class=\"string\">"1.5.0"</span></span><br><span class=\"line\"> <span class=\"keyword\">const</span> <span class=\"keyword\">val</span> JVM = <span class=\"string\">"1.8"</span></span><br><span class=\"line\"> <span class=\"keyword\">const</span> <span class=\"keyword\">val</span> KOTLIN = <span class=\"string\">"1.4.32"</span></span><br><span class=\"line\">}</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"keyword\">object</span> Clikt {</span><br><span class=\"line\"> <span class=\"keyword\">const</span> <span class=\"keyword\">val</span> CLIKT = <span class=\"string\">"com.github.ajalt.clikt:clikt:<span class=\"subst\">${Versions.CLIKT}</span>"</span></span><br><span class=\"line\">}</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"keyword\">object</span> Coroutines {</span><br><span class=\"line\"> <span class=\"keyword\">const</span> <span class=\"keyword\">val</span> CORE = <span class=\"string\">"org.jetbrains.kotlinx:kotlinx-coroutines-core:<span class=\"subst\">${Versions.COROUTINES}</span>"</span></span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n\n<p>Now we can access these in our applications build script. Open the root <code>build.gradle.kts</code> file to add the dependencies and do some version updates:</p>\n<figure class=\"highlight kotlin\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"comment\">// add to the dependencies block</span></span><br><span class=\"line\">dependencies {</span><br><span class=\"line\"> <span class=\"comment\">// clikt</span></span><br><span class=\"line\"> implementation(Clikt.CLIKT)</span><br><span class=\"line\"> <span class=\"comment\">// coroutines</span></span><br><span class=\"line\"> implementation(Coroutines.CORE)</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n\n<p>Optionally, we can update our kotlin version</p>\n<figure class=\"highlight kotlin\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"comment\">// update plugins block</span></span><br><span class=\"line\">plugins {</span><br><span class=\"line\"> application</span><br><span class=\"line\"> kotlin(<span class=\"string\">"jvm"</span>) version Versions.KOTLIN</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n\n<p>Once you start using <code>buildSrc</code> for dependencies you can update many more build script definitions.</p>\n<h2 id=\"Demo-CLI-App\"><a href=\"#Demo-CLI-App\" class=\"headerlink\" title=\"Demo CLI App\"></a>Demo CLI App</h2><p>Now that our project structure is complete we can start build our cli application. We will reate a <code>cli</code> package for our source code and create the following classes/objects:</p>\n<ul>\n<li>Main.kt: Our application entry</li>\n<li>Demo.kt: Our suspendable computation</li>\n<li>DemoConfig.kt: Our applications configurations</li>\n<li>DemoCommand.kt: Our applications teminal commands</li>\n</ul>\n<figure class=\"highlight bash\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"comment\"># create package directory</span></span><br><span class=\"line\">$ mkdir src/main/kotlin/cli && touch src/main/kotlin/cli/Main.kt src/main/kotlin/cli/Demo.kt src/main/kotlin/cli/DemoConfig.kt src/main/kotlin/cli/DemoCommand.kt</span><br></pre></td></tr></table></figure>\n\n<p>The <code>DemoConfig.kt</code> is a utility to house some globals, for now we will use to to reference our app command name. Open <code>DemoConfig.kt</code> to and add the following:</p>\n<figure class=\"highlight kotlin\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">package</span> cli</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"keyword\">object</span> DemoConfig {</span><br><span class=\"line\"> <span class=\"keyword\">val</span> COMMAND_NAME = <span class=\"string\">"demo"</span></span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n\n<p>The <code>DemoCommand.kt</code> is a subclass of <a href=\"https://ajalt.github.io/clikt/api/clikt/com.github.ajalt.clikt.core/-clikt-command/\">CliktCommand</a>. We will set it up with our help page and support for verbose output.</p>\n<figure class=\"highlight kotlin\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br><span class=\"line\">8</span><br><span class=\"line\">9</span><br><span class=\"line\">10</span><br><span class=\"line\">11</span><br><span class=\"line\">12</span><br><span class=\"line\">13</span><br><span class=\"line\">14</span><br><span class=\"line\">15</span><br><span class=\"line\">16</span><br><span class=\"line\">17</span><br><span class=\"line\">18</span><br><span class=\"line\">19</span><br><span class=\"line\">20</span><br><span class=\"line\">21</span><br><span class=\"line\">22</span><br><span class=\"line\">23</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"class\"><span class=\"keyword\">class</span> <span class=\"title\">DemoCommand</span>: <span class=\"type\">CliktCommand</span></span>(</span><br><span class=\"line\"> help = <span class=\"string\">"""</span></span><br><span class=\"line\"><span class=\"string\"> Demo API CLI.</span></span><br><span class=\"line\"><span class=\"string\"> """</span>.trimIndent(),</span><br><span class=\"line\"> epilog = <span class=\"string\">"""</span></span><br><span class=\"line\"><span class=\"string\"> Try it with a demo type</span></span><br><span class=\"line\"><span class=\"string\"></span></span><br><span class=\"line\"><span class=\"string\"> Examples:</span></span><br><span class=\"line\"><span class=\"string\"> <span class=\"subst\">${COMMAND_NAME}</span> hello</span></span><br><span class=\"line\"><span class=\"string\"> """</span>.trimIndent(),</span><br><span class=\"line\"> name = COMMAND_NAME</span><br><span class=\"line\">) {</span><br><span class=\"line\"> <span class=\"keyword\">init</span> {</span><br><span class=\"line\"> completionOption()</span><br><span class=\"line\"> }</span><br><span class=\"line\"></span><br><span class=\"line\"> <span class=\"keyword\">val</span> help: <span class=\"built_in\">Boolean</span> <span class=\"keyword\">by</span> option(<span class=\"string\">"-h"</span>, <span class=\"string\">"--help"</span>, help = <span class=\"string\">"Display this help screen"</span>).flag()</span><br><span class=\"line\"> <span class=\"keyword\">val</span> verbose <span class=\"keyword\">by</span> option(<span class=\"string\">"-v"</span>, <span class=\"string\">"--verbose"</span>, help = <span class=\"string\">"verbose"</span>).flag(defaultForHelp = <span class=\"string\">"disabled"</span>)</span><br><span class=\"line\"></span><br><span class=\"line\"> <span class=\"keyword\">override</span> <span class=\"function\"><span class=\"keyword\">fun</span> <span class=\"title\">run</span><span class=\"params\">()</span></span> {</span><br><span class=\"line\"> <span class=\"keyword\">if</span> (verbose) println(<span class=\"keyword\">this</span>)</span><br><span class=\"line\"> }</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n\n<p>The <code>Demo.kt</code> will have our asynchronous methods to do work, to start we look <code>help</code> in the <code>args</code> passed to the application.</p>\n<figure class=\"highlight kotlin\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br><span class=\"line\">8</span><br><span class=\"line\">9</span><br><span class=\"line\">10</span><br><span class=\"line\">11</span><br><span class=\"line\">12</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">package</span> cli</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"keyword\">suspend</span> <span class=\"function\"><span class=\"keyword\">fun</span> <span class=\"title\">runDemo</span><span class=\"params\">(args: <span class=\"type\">Array</span><<span class=\"type\">String</span>>)</span></span> {</span><br><span class=\"line\"> <span class=\"keyword\">val</span> command = DemoCommand()</span><br><span class=\"line\"></span><br><span class=\"line\"> command.main(args)</span><br><span class=\"line\"></span><br><span class=\"line\"> <span class=\"keyword\">if</span> (command.help) {</span><br><span class=\"line\"> println(command.getFormattedHelp())</span><br><span class=\"line\"> <span class=\"keyword\">return</span></span><br><span class=\"line\"> }</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n\n<p>The <code>Main.kt</code> will be our application entry point. We will run the app in a new coroutine that blocks the current thread until its completion. <code>runBlocking</code> is designed to bridge blocking code to suspending <code>runDemo</code> function.</p>\n<figure class=\"highlight kotlin\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">package</span> cli</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"keyword\">import</span> kotlinx.coroutines.runBlocking</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"function\"><span class=\"keyword\">fun</span> <span class=\"title\">main</span><span class=\"params\">(args: <span class=\"type\">Array</span><<span class=\"type\">String</span>>)</span></span> {</span><br><span class=\"line\"> runBlocking { runDemo(args) }</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n\n<p>With our source code ready we need to define our applications entry point. We can do this by defining our application entry class. Following our <code>buildSrc</code> pattern we can create a new class to source our application configuration:</p>\n<figure class=\"highlight bash\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"comment\"># create Coordinates.kt in buildSrc</span></span><br><span class=\"line\">$ touch buildSrc/src/main/kotlin/Coordinates.kt</span><br></pre></td></tr></table></figure>\n\n<p>Open the newly created <code>Coordinates.kt</code> file and add some application configruateions:</p>\n<figure class=\"highlight kotlin\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">object</span> AppCoordinates {</span><br><span class=\"line\"> <span class=\"keyword\">const</span> <span class=\"keyword\">val</span> APP_ID = <span class=\"string\">"cli.MainKt"</span></span><br><span class=\"line\"></span><br><span class=\"line\"> <span class=\"keyword\">const</span> <span class=\"keyword\">val</span> APP_VERSION_NAME = <span class=\"string\">"0.0.1"</span></span><br><span class=\"line\"> <span class=\"keyword\">const</span> <span class=\"keyword\">val</span> APP_VERSION_CODE = <span class=\"number\">1</span></span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n\n<p>Now we can reference the <code>APP_ID</code> as the main class for our application in the root <code>build.gradle.kts</code> build script, open it and add the following:</p>\n<figure class=\"highlight kotlin\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">application {</span><br><span class=\"line\"> mainClass.<span class=\"keyword\">set</span>(AppCoordinates.APP_ID)</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n\n<h2 id=\"Run-the-app\"><a href=\"#Run-the-app\" class=\"headerlink\" title=\"Run the app\"></a>Run the app</h2><p>At this point the application can be assembled and distributed.</p>\n<figure class=\"highlight bash\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"comment\"># build the app</span></span><br><span class=\"line\">$ ./gradlew assemble</span><br><span class=\"line\"><span class=\"comment\"># build a distribution</span></span><br><span class=\"line\">$ ./gradlew installDist</span><br></pre></td></tr></table></figure>\n\n<p>This will generate an executable in <code>build/install/demo/bin/demo</code>. We can create a run script at the root of the project to expedite and run the application from project root.</p>\n<figure class=\"highlight bash\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"comment\"># create a run script</span></span><br><span class=\"line\">$ touch demo && chmod 755 demo</span><br></pre></td></tr></table></figure>\n\n<p>Open <code>demo</code> and add the following:</p>\n<figure class=\"highlight bash\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"meta\">#!/usr/bin/env bash</span></span><br><span class=\"line\"></span><br><span class=\"line\">./gradlew --quiet <span class=\"string\">"installDist"</span> && <span class=\"string\">"build/install/demo/bin/demo"</span> <span class=\"string\">"<span class=\"variable\">$@</span>"</span></span><br></pre></td></tr></table></figure>\n\n<p>Now we can run <code>./demo --help</code> to see our help page defined in <code>DemoCommand.kt</code></p>\n<figure class=\"highlight bash\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br><span class=\"line\">8</span><br><span class=\"line\">9</span><br><span class=\"line\">10</span><br><span class=\"line\">11</span><br><span class=\"line\">12</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">Usage: demo [OPTIONS]</span><br><span class=\"line\"></span><br><span class=\"line\"> Demo API CLI.</span><br><span class=\"line\"></span><br><span class=\"line\">Options:</span><br><span class=\"line\"> --generate-completion [bash|zsh|fish]</span><br><span class=\"line\"> -h, --<span class=\"built_in\">help</span> Display this <span class=\"built_in\">help</span> screen</span><br><span class=\"line\"> -v, --verbose verbose</span><br><span class=\"line\"></span><br><span class=\"line\">Try it with a demo <span class=\"built_in\">type</span></span><br><span class=\"line\"></span><br><span class=\"line\">Examples: Demo hello</span><br></pre></td></tr></table></figure>\n\n<p>And with that we have successfully skaffold a Kotlin cli application using <code>Clikt</code> and <code>Coroutines</code>, happy coding!</p>\n","site":{"data":{"projects":[{"name":"koop-provider-pg","url":"https://www.npmjs.com/package/koop-provider-pg","desc":"Koop provider to query PostGIS spatial data"},{"name":"Map Preview","url":"https://packagecontrol.io/packages/MapPreview","desc":"Preview map layer files in your web browser using Sublime Text 3"},{"name":"kotlin mulitplatform template","url":"https://github.com/doneill/kotlin-multiplatform-template","desc":"Kotlin MPP template to bootstrap Mobile/Web"}]}},"excerpt":"","more":"<p>Automating things via command line tools is awesome and after doing some experimenting with Kotlin as a modern programming language for cli applications and inspired by the <a href=\"https://github.com/jmfayard/kotlin-cli-starter\">kotlin cli starter repo</a> I wanted to break down creating the cli application from scratch using <a href=\"https://ajalt.github.io/clikt/\">CLIKT</a> and <a href=\"https://kotlinlang.org/docs/coroutines-overview.html\">Coroutines</a>.</p>\n<h2 id=\"Create-a-single-module-Gradle-project\"><a href=\"#Create-a-single-module-Gradle-project\" class=\"headerlink\" title=\"Create a single module Gradle project\"></a>Create a single module Gradle project</h2><p>Create a project folder and skaffold it a Gradle application project:</p>\n<figure class=\"highlight bash\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"comment\"># create your poject directory</span></span><br><span class=\"line\">$ mkdir demo</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"comment\"># initialize it with Gradle project structure</span></span><br><span class=\"line\">$ <span class=\"built_in\">cd</span> demo && gradle init</span><br></pre></td></tr></table></figure>\n\n<p>Choose the following project settings when prompted:</p>\n<figure class=\"highlight bash\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br><span class=\"line\">8</span><br><span class=\"line\">9</span><br><span class=\"line\">10</span><br><span class=\"line\">11</span><br><span class=\"line\">12</span><br><span class=\"line\">13</span><br><span class=\"line\">14</span><br><span class=\"line\">15</span><br><span class=\"line\">16</span><br><span class=\"line\">17</span><br><span class=\"line\">18</span><br><span class=\"line\">19</span><br><span class=\"line\">20</span><br><span class=\"line\">21</span><br><span class=\"line\">22</span><br><span class=\"line\">23</span><br><span class=\"line\">24</span><br><span class=\"line\">25</span><br><span class=\"line\">26</span><br><span class=\"line\">27</span><br><span class=\"line\">28</span><br><span class=\"line\">29</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"comment\"># select 2 application project</span></span><br><span class=\"line\">Select <span class=\"built_in\">type</span> of project to generate:</span><br><span class=\"line\"> 1: basic</span><br><span class=\"line\"> 2: application</span><br><span class=\"line\"> 3: library</span><br><span class=\"line\"> 4: Gradle plugin</span><br><span class=\"line\">Enter selection (default: basic) [1..4] 2</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"comment\"># Kotlin implementation language</span></span><br><span class=\"line\">Select implementation language:</span><br><span class=\"line\"> 1: C++</span><br><span class=\"line\"> 2: Groovy</span><br><span class=\"line\"> 3: Java</span><br><span class=\"line\"> 4: Kotlin</span><br><span class=\"line\"> 5: Scala</span><br><span class=\"line\"> 6: Swift</span><br><span class=\"line\">Enter selection (default: Java) [1..6] 4</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"comment\"># simple on application project</span></span><br><span class=\"line\">Split functionality across multiple subprojects?:</span><br><span class=\"line\"> 1: no - only one application project</span><br><span class=\"line\"> 2: yes - application and library projects</span><br><span class=\"line\">Enter selection (default: no - only one application project) [1..2] 1</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"comment\"># kotlin dsl</span></span><br><span class=\"line\">Select build script DSL:</span><br><span class=\"line\"> 1: Groovy</span><br><span class=\"line\"> 2: Kotlin</span><br><span class=\"line\">Enter selection (default: Kotlin) [1..2] 2</span><br></pre></td></tr></table></figure>\n\n<p>Now we have an empty application project. Let’s clean it up a bit. The application structure created a single module project, we can remove the module and use the root projct as the module source:</p>\n<figure class=\"highlight bash\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"comment\"># move the build script to root project</span></span><br><span class=\"line\">$ mv app/build.gradle.kts build.gradle.kts</span><br><span class=\"line\"><span class=\"comment\"># remove app module, use project root as module source</span></span><br><span class=\"line\">$ mv app/src src && rm -rf app</span><br></pre></td></tr></table></figure>\n\n<p>Open <code>settings.gradle.kts</code> and remove the module reference <code>include("app")</code>. Your app directory structure should look simiilar to the following:</p>\n<p align=\"rigth\">\n <img align=\"left\" width=\"300\" height=\"244\" src=\"app-structure.png\">\n</p>\n\n<h2 id=\"Dependencies\"><a href=\"#Dependencies\" class=\"headerlink\" title=\"Dependencies\"></a>Dependencies</h2><p>A common way to manage gradle dependencies is to have an <code>ext{}</code> block in your root project. This doesn’t provide any code completion or code navigation, in order to suppport those features we will define dependencies as String constants in <code>Dependencies.kt</code> inside of <code>buildSrc</code> folder. The directory <code>buildSrc</code> is treated as an included build where Gradle automatically compiles its sources.</p>\n<figure class=\"highlight bash\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"comment\"># make a buildSrc directory and source package structure</span></span><br><span class=\"line\">$ mkdir -p buildSrc/{src/{main/{kotlin,},},}</span><br><span class=\"line\"><span class=\"comment\"># create Dependencies.kt</span></span><br><span class=\"line\">$ touch buildSrc/src/main/kotlin/Dependencies.kt</span><br></pre></td></tr></table></figure>\n\n<p>Now we need to create a build script for our Dependencies</p>\n<figure class=\"highlight bash\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">$ touch buildSrc/build.gradle.kts</span><br></pre></td></tr></table></figure>\n\n<p>Open the new <code>build.gradle.kts</code> file and add the following:</p>\n<figure class=\"highlight kotlin\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">plugins {</span><br><span class=\"line\"> `kotlin-dsl`</span><br><span class=\"line\">}</span><br><span class=\"line\"></span><br><span class=\"line\">repositories {</span><br><span class=\"line\"> mavenCentral()</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n\n<h3 id=\"Asynchronous-Command-Line-Iterface-for-Kotlin\"><a href=\"#Asynchronous-Command-Line-Iterface-for-Kotlin\" class=\"headerlink\" title=\"Asynchronous Command Line Iterface for Kotlin\"></a>Asynchronous Command Line Iterface for Kotlin</h3><p>We will use <code>CLIKT</code> multiplatform library which makes writing cli apps easy and <code>Coroutines</code> for an asynchronous, or non-blocking, application. Open <code>buildSrc/src/main/kotlin/Dependencies.kt</code> file to add the following dependencies and versions:</p>\n<figure class=\"highlight kotlin\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br><span class=\"line\">8</span><br><span class=\"line\">9</span><br><span class=\"line\">10</span><br><span class=\"line\">11</span><br><span class=\"line\">12</span><br><span class=\"line\">13</span><br><span class=\"line\">14</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">object</span> Versions {</span><br><span class=\"line\"> <span class=\"keyword\">const</span> <span class=\"keyword\">val</span> CLIKT = <span class=\"string\">"3.2.0"</span></span><br><span class=\"line\"> <span class=\"keyword\">const</span> <span class=\"keyword\">val</span> COROUTINES = <span class=\"string\">"1.5.0"</span></span><br><span class=\"line\"> <span class=\"keyword\">const</span> <span class=\"keyword\">val</span> JVM = <span class=\"string\">"1.8"</span></span><br><span class=\"line\"> <span class=\"keyword\">const</span> <span class=\"keyword\">val</span> KOTLIN = <span class=\"string\">"1.4.32"</span></span><br><span class=\"line\">}</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"keyword\">object</span> Clikt {</span><br><span class=\"line\"> <span class=\"keyword\">const</span> <span class=\"keyword\">val</span> CLIKT = <span class=\"string\">"com.github.ajalt.clikt:clikt:<span class=\"subst\">${Versions.CLIKT}</span>"</span></span><br><span class=\"line\">}</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"keyword\">object</span> Coroutines {</span><br><span class=\"line\"> <span class=\"keyword\">const</span> <span class=\"keyword\">val</span> CORE = <span class=\"string\">"org.jetbrains.kotlinx:kotlinx-coroutines-core:<span class=\"subst\">${Versions.COROUTINES}</span>"</span></span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n\n<p>Now we can access these in our applications build script. Open the root <code>build.gradle.kts</code> file to add the dependencies and do some version updates:</p>\n<figure class=\"highlight kotlin\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"comment\">// add to the dependencies block</span></span><br><span class=\"line\">dependencies {</span><br><span class=\"line\"> <span class=\"comment\">// clikt</span></span><br><span class=\"line\"> implementation(Clikt.CLIKT)</span><br><span class=\"line\"> <span class=\"comment\">// coroutines</span></span><br><span class=\"line\"> implementation(Coroutines.CORE)</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n\n<p>Optionally, we can update our kotlin version</p>\n<figure class=\"highlight kotlin\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"comment\">// update plugins block</span></span><br><span class=\"line\">plugins {</span><br><span class=\"line\"> application</span><br><span class=\"line\"> kotlin(<span class=\"string\">"jvm"</span>) version Versions.KOTLIN</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n\n<p>Once you start using <code>buildSrc</code> for dependencies you can update many more build script definitions.</p>\n<h2 id=\"Demo-CLI-App\"><a href=\"#Demo-CLI-App\" class=\"headerlink\" title=\"Demo CLI App\"></a>Demo CLI App</h2><p>Now that our project structure is complete we can start build our cli application. We will reate a <code>cli</code> package for our source code and create the following classes/objects:</p>\n<ul>\n<li>Main.kt: Our application entry</li>\n<li>Demo.kt: Our suspendable computation</li>\n<li>DemoConfig.kt: Our applications configurations</li>\n<li>DemoCommand.kt: Our applications teminal commands</li>\n</ul>\n<figure class=\"highlight bash\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"comment\"># create package directory</span></span><br><span class=\"line\">$ mkdir src/main/kotlin/cli && touch src/main/kotlin/cli/Main.kt src/main/kotlin/cli/Demo.kt src/main/kotlin/cli/DemoConfig.kt src/main/kotlin/cli/DemoCommand.kt</span><br></pre></td></tr></table></figure>\n\n<p>The <code>DemoConfig.kt</code> is a utility to house some globals, for now we will use to to reference our app command name. Open <code>DemoConfig.kt</code> to and add the following:</p>\n<figure class=\"highlight kotlin\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">package</span> cli</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"keyword\">object</span> DemoConfig {</span><br><span class=\"line\"> <span class=\"keyword\">val</span> COMMAND_NAME = <span class=\"string\">"demo"</span></span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n\n<p>The <code>DemoCommand.kt</code> is a subclass of <a href=\"https://ajalt.github.io/clikt/api/clikt/com.github.ajalt.clikt.core/-clikt-command/\">CliktCommand</a>. We will set it up with our help page and support for verbose output.</p>\n<figure class=\"highlight kotlin\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br><span class=\"line\">8</span><br><span class=\"line\">9</span><br><span class=\"line\">10</span><br><span class=\"line\">11</span><br><span class=\"line\">12</span><br><span class=\"line\">13</span><br><span class=\"line\">14</span><br><span class=\"line\">15</span><br><span class=\"line\">16</span><br><span class=\"line\">17</span><br><span class=\"line\">18</span><br><span class=\"line\">19</span><br><span class=\"line\">20</span><br><span class=\"line\">21</span><br><span class=\"line\">22</span><br><span class=\"line\">23</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"class\"><span class=\"keyword\">class</span> <span class=\"title\">DemoCommand</span>: <span class=\"type\">CliktCommand</span></span>(</span><br><span class=\"line\"> help = <span class=\"string\">"""</span></span><br><span class=\"line\"><span class=\"string\"> Demo API CLI.</span></span><br><span class=\"line\"><span class=\"string\"> """</span>.trimIndent(),</span><br><span class=\"line\"> epilog = <span class=\"string\">"""</span></span><br><span class=\"line\"><span class=\"string\"> Try it with a demo type</span></span><br><span class=\"line\"><span class=\"string\"></span></span><br><span class=\"line\"><span class=\"string\"> Examples:</span></span><br><span class=\"line\"><span class=\"string\"> <span class=\"subst\">${COMMAND_NAME}</span> hello</span></span><br><span class=\"line\"><span class=\"string\"> """</span>.trimIndent(),</span><br><span class=\"line\"> name = COMMAND_NAME</span><br><span class=\"line\">) {</span><br><span class=\"line\"> <span class=\"keyword\">init</span> {</span><br><span class=\"line\"> completionOption()</span><br><span class=\"line\"> }</span><br><span class=\"line\"></span><br><span class=\"line\"> <span class=\"keyword\">val</span> help: <span class=\"built_in\">Boolean</span> <span class=\"keyword\">by</span> option(<span class=\"string\">"-h"</span>, <span class=\"string\">"--help"</span>, help = <span class=\"string\">"Display this help screen"</span>).flag()</span><br><span class=\"line\"> <span class=\"keyword\">val</span> verbose <span class=\"keyword\">by</span> option(<span class=\"string\">"-v"</span>, <span class=\"string\">"--verbose"</span>, help = <span class=\"string\">"verbose"</span>).flag(defaultForHelp = <span class=\"string\">"disabled"</span>)</span><br><span class=\"line\"></span><br><span class=\"line\"> <span class=\"keyword\">override</span> <span class=\"function\"><span class=\"keyword\">fun</span> <span class=\"title\">run</span><span class=\"params\">()</span></span> {</span><br><span class=\"line\"> <span class=\"keyword\">if</span> (verbose) println(<span class=\"keyword\">this</span>)</span><br><span class=\"line\"> }</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n\n<p>The <code>Demo.kt</code> will have our asynchronous methods to do work, to start we look <code>help</code> in the <code>args</code> passed to the application.</p>\n<figure class=\"highlight kotlin\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br><span class=\"line\">8</span><br><span class=\"line\">9</span><br><span class=\"line\">10</span><br><span class=\"line\">11</span><br><span class=\"line\">12</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">package</span> cli</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"keyword\">suspend</span> <span class=\"function\"><span class=\"keyword\">fun</span> <span class=\"title\">runDemo</span><span class=\"params\">(args: <span class=\"type\">Array</span><<span class=\"type\">String</span>>)</span></span> {</span><br><span class=\"line\"> <span class=\"keyword\">val</span> command = DemoCommand()</span><br><span class=\"line\"></span><br><span class=\"line\"> command.main(args)</span><br><span class=\"line\"></span><br><span class=\"line\"> <span class=\"keyword\">if</span> (command.help) {</span><br><span class=\"line\"> println(command.getFormattedHelp())</span><br><span class=\"line\"> <span class=\"keyword\">return</span></span><br><span class=\"line\"> }</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n\n<p>The <code>Main.kt</code> will be our application entry point. We will run the app in a new coroutine that blocks the current thread until its completion. <code>runBlocking</code> is designed to bridge blocking code to suspending <code>runDemo</code> function.</p>\n<figure class=\"highlight kotlin\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">package</span> cli</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"keyword\">import</span> kotlinx.coroutines.runBlocking</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"function\"><span class=\"keyword\">fun</span> <span class=\"title\">main</span><span class=\"params\">(args: <span class=\"type\">Array</span><<span class=\"type\">String</span>>)</span></span> {</span><br><span class=\"line\"> runBlocking { runDemo(args) }</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n\n<p>With our source code ready we need to define our applications entry point. We can do this by defining our application entry class. Following our <code>buildSrc</code> pattern we can create a new class to source our application configuration:</p>\n<figure class=\"highlight bash\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"comment\"># create Coordinates.kt in buildSrc</span></span><br><span class=\"line\">$ touch buildSrc/src/main/kotlin/Coordinates.kt</span><br></pre></td></tr></table></figure>\n\n<p>Open the newly created <code>Coordinates.kt</code> file and add some application configruateions:</p>\n<figure class=\"highlight kotlin\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">object</span> AppCoordinates {</span><br><span class=\"line\"> <span class=\"keyword\">const</span> <span class=\"keyword\">val</span> APP_ID = <span class=\"string\">"cli.MainKt"</span></span><br><span class=\"line\"></span><br><span class=\"line\"> <span class=\"keyword\">const</span> <span class=\"keyword\">val</span> APP_VERSION_NAME = <span class=\"string\">"0.0.1"</span></span><br><span class=\"line\"> <span class=\"keyword\">const</span> <span class=\"keyword\">val</span> APP_VERSION_CODE = <span class=\"number\">1</span></span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n\n<p>Now we can reference the <code>APP_ID</code> as the main class for our application in the root <code>build.gradle.kts</code> build script, open it and add the following:</p>\n<figure class=\"highlight kotlin\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">application {</span><br><span class=\"line\"> mainClass.<span class=\"keyword\">set</span>(AppCoordinates.APP_ID)</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n\n<h2 id=\"Run-the-app\"><a href=\"#Run-the-app\" class=\"headerlink\" title=\"Run the app\"></a>Run the app</h2><p>At this point the application can be assembled and distributed.</p>\n<figure class=\"highlight bash\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"comment\"># build the app</span></span><br><span class=\"line\">$ ./gradlew assemble</span><br><span class=\"line\"><span class=\"comment\"># build a distribution</span></span><br><span class=\"line\">$ ./gradlew installDist</span><br></pre></td></tr></table></figure>\n\n<p>This will generate an executable in <code>build/install/demo/bin/demo</code>. We can create a run script at the root of the project to expedite and run the application from project root.</p>\n<figure class=\"highlight bash\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"comment\"># create a run script</span></span><br><span class=\"line\">$ touch demo && chmod 755 demo</span><br></pre></td></tr></table></figure>\n\n<p>Open <code>demo</code> and add the following:</p>\n<figure class=\"highlight bash\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"meta\">#!/usr/bin/env bash</span></span><br><span class=\"line\"></span><br><span class=\"line\">./gradlew --quiet <span class=\"string\">"installDist"</span> && <span class=\"string\">"build/install/demo/bin/demo"</span> <span class=\"string\">"<span class=\"variable\">$@</span>"</span></span><br></pre></td></tr></table></figure>\n\n<p>Now we can run <code>./demo --help</code> to see our help page defined in <code>DemoCommand.kt</code></p>\n<figure class=\"highlight bash\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br><span class=\"line\">8</span><br><span class=\"line\">9</span><br><span class=\"line\">10</span><br><span class=\"line\">11</span><br><span class=\"line\">12</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">Usage: demo [OPTIONS]</span><br><span class=\"line\"></span><br><span class=\"line\"> Demo API CLI.</span><br><span class=\"line\"></span><br><span class=\"line\">Options:</span><br><span class=\"line\"> --generate-completion [bash|zsh|fish]</span><br><span class=\"line\"> -h, --<span class=\"built_in\">help</span> Display this <span class=\"built_in\">help</span> screen</span><br><span class=\"line\"> -v, --verbose verbose</span><br><span class=\"line\"></span><br><span class=\"line\">Try it with a demo <span class=\"built_in\">type</span></span><br><span class=\"line\"></span><br><span class=\"line\">Examples: Demo hello</span><br></pre></td></tr></table></figure>\n\n<p>And with that we have successfully skaffold a Kotlin cli application using <code>Clikt</code> and <code>Coroutines</code>, happy coding!</p>\n"},{"title":"Multiplatform Persistence with SQLDelight","date":"2020-06-29T01:18:25.000Z","_content":"\n## SQLDelight\n[SQLDelight](https://cashapp.github.io/sqldelight/) provides libraries and drivers to generate typesafe Kotlin classes from SQL statements that verifies database schemas, statements, and migrations at compile time. This post will step through the basics of getting started with SQLDelight for Android and iOS using the [kotlin-mulitplatform-template](https://github.com/doneill/kotlin-multiplatform-template) I referenced in a previous [post](http://gh.jdoneill.com/2019/12/06/kotlin-mulitplatform/), a project that started out as a template for getting your development environment started with Kotlin Multiplatform.\n\n## Dependency setup\n\nFirst we will get started adding the most basic dependencies, adding the plugin classpath to the project, the common library dependencies, and Android app dependencies.\n\n```kotlin\nbuildscript {\n repositories {\n google()\n mavenCentral()\n jcenter()\n }\n dependencies {\n classpath(SqlDelight.GRADLE)\n }\n}\n```\n\nIn your common gradle build file apply the plugin and add platform dependencies.\n\n```kotlin\n// apply plugin\nplugins {\n ...\n id(\"com.squareup.sqldelight\")\n}\n\n// commonMain dependency\nimplementation(\"com.squareup.sqldelight:runtime:${Versions.SQLDELIGHT}\")\n\n// androidMain dependency\nimplementation(\"com.squareup.sqldelight:android-driver:${Versions.SQLDELIGHT}\")\n\n// iosMain dependency\nimplementation(\"com.squareup.sqldelight:native-driver:${Versions.SQLDELIGHT}\")\n\n```\n\nIn your Android app gradle build file.\n\n```kotlin\nimplementation (com.squareup.sqldelight:android-driver:${Versions.SQLDELIGHT})\n```\n\n### Databse schema setup\n\nSetup your database schema dependencies. Generically this looks for a database named `MyDatabase` in `com.example.package` package directory structure. More on this in the next section.\n\n```kotlin\nsqldelight {\n database(\"MyDatabase\") {\n packageName = \"com.example.package\"\n sourceFolders = listOf(\"sqldelight\")\n }\n}\n```\n\nAnd that's it for a very basic setup, check out this [commit](https://github.com/doneill/kotlin-multiplatform-template/commit/ae5974a63a0018a26e8a89040091fb4cafb8af9a) for reference.\n\n## Implementation\n\nSQLDelight generates Kotlin source files which can be used to create and interact with the database. Create your initial database schema with a `*.sq` file. This file always represents the latest schema for an empty database. You need to create the SQL source file in the package defined above in the `commonMain` sourceset, e.g. **common/src/commonMain/sqldelight/com/example/package**. In this example we will be persisting some weather station data.\n\n```sql\nCREATE TABLE Weather (\n id INTEGER NOT NULL PRIMARY KEY,\n name TEXT NOT NULL,\n latest_temp REAL NOT NULL DEFAULT 0,\n timestamp TEXT NOT NULL\n);\n\nselectAll:\nSELECT * FROM Weather;\n\nselectById:\nSELECT * FROM Weather WHERE id = ?;\n\nselectByName:\nSELECT * FROM Weather WHERE name = ?;\n\ninsertWeather:\nINSERT OR REPLACE INTO Weather(id, name, latest_temp, timestamp)\nVALUES (?,?,?,?);\n\ndeleteAll:\nDELETE FROM Weather;\n```\n\n### Drivers\n\nImplement the platform driver factories to create the database on both Android and iOS platforms. Include the following inside **common/src/commonMain/kotlin/** source set:\n\n```kotlin\nexpect class KmpDriverFactory {\n fun createDriver(): SqlDriver\n}\n\nfun createDb(kmpDriverFactory: KmpDriverFactory): KmpDb {\n val driver = kmpDriverFactory.createDriver()\n\n return KmpDb(driver)\n}\n```\n\nImplement the Android `actual` in **common/src/AndroidMain/kotlin**:\n\n```kotlin\nactual class KmpDriverFactory(private val appContext: Context) {\n actual fun createDriver(): SqlDriver {\n return AndroidSqliteDriver(KmpDb.Schema, appContext, \"kmp.db\")\n }\n}\n```\n\nImplement the iOS `actual` in **common/src/iosMain/kotlin**:\n\n```kotlin\nactual class KmpDriverFactory {\n actual fun createDriver(): SqlDriver {\n return NativeSqliteDriver(KmpDb.Schema, \"kmp.db\")\n }\n}\n```\n\n### Client access\n\nIn order to show interacting with the SQLite database we add code in `MainActivity`, which is not where this would typically go but it validates the process. The `insertWeather` and `selectAll` methods are generated for us.\n\n```kotlin\nval driver = KmpDriverFactory(this)\nval db = createDb(driver)\nval kmpQuery = db.kmpModelQueries\n\nkmpQuery.insertWeather(id, name, temp.toDouble(), getDate())\nval results = kmpQuery.selectAll().executeAsList()\n\nfor (result in results) {\n // do something with result\n}\n```\n\nSwift code for iOS can be used to call Kotlin `KmpDriverFactory()` and `createDb` to create the database and validate the process on iOS.\n\n\n```Swift\nlet driver = KmpDriverFactory()\nlet db = KmpDriverFactoryKt.createDb(kmpDriverFactory: driver)\nlet kmpQuery = db.kmpModelQueries\n\nkmpQuery.insertWeather(id: KotlinLong(value: id), name: name, latest_temp: Double(temp), timestamp: DateUtilsKt.getDate())\nlet results = kmpQuery.selectAll().executeAsList()\n\nfor result in results {\n // do something with result\n}\n```\n\nAnd that's it for a very basic implementation, check out this [commit](https://github.com/doneill/kotlin-multiplatform-template/commit/0e8e69e6c486379811e7affd06986ab2ac7c98c6) for reference.\n\n## Resources\n\nSQLDelight IntelliJ PLugin is available from within Android Studio by navigating to **Preferences > Plugins > Marketplace > Search for SQLDelight**\n\n- [Introduction to Multiplatform Persistence with SQLDelight](https://johnoreilly.dev/posts/sqldelight-multiplatform/)\n- [SQLDelight 1.x Quick Start Guide for Android](https://handstandsam.com/2019/08/23/sqldelight-1-x-quick-start-guide-for-android/)\n- [A Multiplatform Delight](https://www.youtube.com/watch?v=WkIry790PHI)\n\n","source":"_posts/sqldelight.md","raw":"---\ntitle: Multiplatform Persistence with SQLDelight\ndate: 2020-06-28 18:18:25\ntags:\n- kotlin\n- multiplatform\n---\n\n## SQLDelight\n[SQLDelight](https://cashapp.github.io/sqldelight/) provides libraries and drivers to generate typesafe Kotlin classes from SQL statements that verifies database schemas, statements, and migrations at compile time. This post will step through the basics of getting started with SQLDelight for Android and iOS using the [kotlin-mulitplatform-template](https://github.com/doneill/kotlin-multiplatform-template) I referenced in a previous [post](http://gh.jdoneill.com/2019/12/06/kotlin-mulitplatform/), a project that started out as a template for getting your development environment started with Kotlin Multiplatform.\n\n## Dependency setup\n\nFirst we will get started adding the most basic dependencies, adding the plugin classpath to the project, the common library dependencies, and Android app dependencies.\n\n```kotlin\nbuildscript {\n repositories {\n google()\n mavenCentral()\n jcenter()\n }\n dependencies {\n classpath(SqlDelight.GRADLE)\n }\n}\n```\n\nIn your common gradle build file apply the plugin and add platform dependencies.\n\n```kotlin\n// apply plugin\nplugins {\n ...\n id(\"com.squareup.sqldelight\")\n}\n\n// commonMain dependency\nimplementation(\"com.squareup.sqldelight:runtime:${Versions.SQLDELIGHT}\")\n\n// androidMain dependency\nimplementation(\"com.squareup.sqldelight:android-driver:${Versions.SQLDELIGHT}\")\n\n// iosMain dependency\nimplementation(\"com.squareup.sqldelight:native-driver:${Versions.SQLDELIGHT}\")\n\n```\n\nIn your Android app gradle build file.\n\n```kotlin\nimplementation (com.squareup.sqldelight:android-driver:${Versions.SQLDELIGHT})\n```\n\n### Databse schema setup\n\nSetup your database schema dependencies. Generically this looks for a database named `MyDatabase` in `com.example.package` package directory structure. More on this in the next section.\n\n```kotlin\nsqldelight {\n database(\"MyDatabase\") {\n packageName = \"com.example.package\"\n sourceFolders = listOf(\"sqldelight\")\n }\n}\n```\n\nAnd that's it for a very basic setup, check out this [commit](https://github.com/doneill/kotlin-multiplatform-template/commit/ae5974a63a0018a26e8a89040091fb4cafb8af9a) for reference.\n\n## Implementation\n\nSQLDelight generates Kotlin source files which can be used to create and interact with the database. Create your initial database schema with a `*.sq` file. This file always represents the latest schema for an empty database. You need to create the SQL source file in the package defined above in the `commonMain` sourceset, e.g. **common/src/commonMain/sqldelight/com/example/package**. In this example we will be persisting some weather station data.\n\n```sql\nCREATE TABLE Weather (\n id INTEGER NOT NULL PRIMARY KEY,\n name TEXT NOT NULL,\n latest_temp REAL NOT NULL DEFAULT 0,\n timestamp TEXT NOT NULL\n);\n\nselectAll:\nSELECT * FROM Weather;\n\nselectById:\nSELECT * FROM Weather WHERE id = ?;\n\nselectByName:\nSELECT * FROM Weather WHERE name = ?;\n\ninsertWeather:\nINSERT OR REPLACE INTO Weather(id, name, latest_temp, timestamp)\nVALUES (?,?,?,?);\n\ndeleteAll:\nDELETE FROM Weather;\n```\n\n### Drivers\n\nImplement the platform driver factories to create the database on both Android and iOS platforms. Include the following inside **common/src/commonMain/kotlin/** source set:\n\n```kotlin\nexpect class KmpDriverFactory {\n fun createDriver(): SqlDriver\n}\n\nfun createDb(kmpDriverFactory: KmpDriverFactory): KmpDb {\n val driver = kmpDriverFactory.createDriver()\n\n return KmpDb(driver)\n}\n```\n\nImplement the Android `actual` in **common/src/AndroidMain/kotlin**:\n\n```kotlin\nactual class KmpDriverFactory(private val appContext: Context) {\n actual fun createDriver(): SqlDriver {\n return AndroidSqliteDriver(KmpDb.Schema, appContext, \"kmp.db\")\n }\n}\n```\n\nImplement the iOS `actual` in **common/src/iosMain/kotlin**:\n\n```kotlin\nactual class KmpDriverFactory {\n actual fun createDriver(): SqlDriver {\n return NativeSqliteDriver(KmpDb.Schema, \"kmp.db\")\n }\n}\n```\n\n### Client access\n\nIn order to show interacting with the SQLite database we add code in `MainActivity`, which is not where this would typically go but it validates the process. The `insertWeather` and `selectAll` methods are generated for us.\n\n```kotlin\nval driver = KmpDriverFactory(this)\nval db = createDb(driver)\nval kmpQuery = db.kmpModelQueries\n\nkmpQuery.insertWeather(id, name, temp.toDouble(), getDate())\nval results = kmpQuery.selectAll().executeAsList()\n\nfor (result in results) {\n // do something with result\n}\n```\n\nSwift code for iOS can be used to call Kotlin `KmpDriverFactory()` and `createDb` to create the database and validate the process on iOS.\n\n\n```Swift\nlet driver = KmpDriverFactory()\nlet db = KmpDriverFactoryKt.createDb(kmpDriverFactory: driver)\nlet kmpQuery = db.kmpModelQueries\n\nkmpQuery.insertWeather(id: KotlinLong(value: id), name: name, latest_temp: Double(temp), timestamp: DateUtilsKt.getDate())\nlet results = kmpQuery.selectAll().executeAsList()\n\nfor result in results {\n // do something with result\n}\n```\n\nAnd that's it for a very basic implementation, check out this [commit](https://github.com/doneill/kotlin-multiplatform-template/commit/0e8e69e6c486379811e7affd06986ab2ac7c98c6) for reference.\n\n## Resources\n\nSQLDelight IntelliJ PLugin is available from within Android Studio by navigating to **Preferences > Plugins > Marketplace > Search for SQLDelight**\n\n- [Introduction to Multiplatform Persistence with SQLDelight](https://johnoreilly.dev/posts/sqldelight-multiplatform/)\n- [SQLDelight 1.x Quick Start Guide for Android](https://handstandsam.com/2019/08/23/sqldelight-1-x-quick-start-guide-for-android/)\n- [A Multiplatform Delight](https://www.youtube.com/watch?v=WkIry790PHI)\n\n","slug":"sqldelight","published":1,"updated":"2020-06-29T01:18:25.000Z","comments":1,"layout":"post","photos":[],"link":"","_id":"ckz0jkmd1000mnvhw3s5q42jg","content":"<h2 id=\"SQLDelight\"><a href=\"#SQLDelight\" class=\"headerlink\" title=\"SQLDelight\"></a>SQLDelight</h2><p><a href=\"https://cashapp.github.io/sqldelight/\">SQLDelight</a> provides libraries and drivers to generate typesafe Kotlin classes from SQL statements that verifies database schemas, statements, and migrations at compile time. This post will step through the basics of getting started with SQLDelight for Android and iOS using the <a href=\"https://github.com/doneill/kotlin-multiplatform-template\">kotlin-mulitplatform-template</a> I referenced in a previous <a href=\"http://gh.jdoneill.com/2019/12/06/kotlin-mulitplatform/\">post</a>, a project that started out as a template for getting your development environment started with Kotlin Multiplatform.</p>\n<h2 id=\"Dependency-setup\"><a href=\"#Dependency-setup\" class=\"headerlink\" title=\"Dependency setup\"></a>Dependency setup</h2><p>First we will get started adding the most basic dependencies, adding the plugin classpath to the project, the common library dependencies, and Android app dependencies.</p>\n<figure class=\"highlight kotlin\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br><span class=\"line\">8</span><br><span class=\"line\">9</span><br><span class=\"line\">10</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">buildscript {</span><br><span class=\"line\"> repositories {</span><br><span class=\"line\"> google()</span><br><span class=\"line\"> mavenCentral()</span><br><span class=\"line\"> jcenter()</span><br><span class=\"line\"> }</span><br><span class=\"line\"> dependencies {</span><br><span class=\"line\"> classpath(SqlDelight.GRADLE)</span><br><span class=\"line\"> }</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n\n<p>In your common gradle build file apply the plugin and add platform dependencies.</p>\n<figure class=\"highlight kotlin\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br><span class=\"line\">8</span><br><span class=\"line\">9</span><br><span class=\"line\">10</span><br><span class=\"line\">11</span><br><span class=\"line\">12</span><br><span class=\"line\">13</span><br><span class=\"line\">14</span><br><span class=\"line\">15</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"comment\">// apply plugin</span></span><br><span class=\"line\">plugins {</span><br><span class=\"line\"> ...</span><br><span class=\"line\"> id(<span class=\"string\">"com.squareup.sqldelight"</span>)</span><br><span class=\"line\">}</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"comment\">// commonMain dependency</span></span><br><span class=\"line\">implementation(<span class=\"string\">"com.squareup.sqldelight:runtime:<span class=\"subst\">${Versions.SQLDELIGHT}</span>"</span>)</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"comment\">// androidMain dependency</span></span><br><span class=\"line\">implementation(<span class=\"string\">"com.squareup.sqldelight:android-driver:<span class=\"subst\">${Versions.SQLDELIGHT}</span>"</span>)</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"comment\">// iosMain dependency</span></span><br><span class=\"line\">implementation(<span class=\"string\">"com.squareup.sqldelight:native-driver:<span class=\"subst\">${Versions.SQLDELIGHT}</span>"</span>)</span><br><span class=\"line\"></span><br></pre></td></tr></table></figure>\n\n<p>In your Android app gradle build file.</p>\n<figure class=\"highlight kotlin\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">implementation (com.squareup.sqldelight:android-driver:${Versions.SQLDELIGHT})</span><br></pre></td></tr></table></figure>\n\n<h3 id=\"Databse-schema-setup\"><a href=\"#Databse-schema-setup\" class=\"headerlink\" title=\"Databse schema setup\"></a>Databse schema setup</h3><p>Setup your database schema dependencies. Generically this looks for a database named <code>MyDatabase</code> in <code>com.example.package</code> package directory structure. More on this in the next section.</p>\n<figure class=\"highlight kotlin\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">sqldelight {</span><br><span class=\"line\"> database(<span class=\"string\">"MyDatabase"</span>) {</span><br><span class=\"line\"> packageName = <span class=\"string\">"com.example.package"</span></span><br><span class=\"line\"> sourceFolders = listOf(<span class=\"string\">"sqldelight"</span>)</span><br><span class=\"line\"> }</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n\n<p>And that’s it for a very basic setup, check out this <a href=\"https://github.com/doneill/kotlin-multiplatform-template/commit/ae5974a63a0018a26e8a89040091fb4cafb8af9a\">commit</a> for reference.</p>\n<h2 id=\"Implementation\"><a href=\"#Implementation\" class=\"headerlink\" title=\"Implementation\"></a>Implementation</h2><p>SQLDelight generates Kotlin source files which can be used to create and interact with the database. Create your initial database schema with a <code>*.sq</code> file. This file always represents the latest schema for an empty database. You need to create the SQL source file in the package defined above in the <code>commonMain</code> sourceset, e.g. <strong>common/src/commonMain/sqldelight/com/example/package</strong>. In this example we will be persisting some weather station data.</p>\n<figure class=\"highlight sql\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br><span class=\"line\">8</span><br><span class=\"line\">9</span><br><span class=\"line\">10</span><br><span class=\"line\">11</span><br><span class=\"line\">12</span><br><span class=\"line\">13</span><br><span class=\"line\">14</span><br><span class=\"line\">15</span><br><span class=\"line\">16</span><br><span class=\"line\">17</span><br><span class=\"line\">18</span><br><span class=\"line\">19</span><br><span class=\"line\">20</span><br><span class=\"line\">21</span><br><span class=\"line\">22</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">CREATE</span> <span class=\"keyword\">TABLE</span> Weather (</span><br><span class=\"line\"> id <span class=\"type\">INTEGER</span> <span class=\"keyword\">NOT</span> <span class=\"keyword\">NULL</span> <span class=\"keyword\">PRIMARY</span> KEY,</span><br><span class=\"line\"> name TEXT <span class=\"keyword\">NOT</span> <span class=\"keyword\">NULL</span>,</span><br><span class=\"line\"> latest_temp <span class=\"type\">REAL</span> <span class=\"keyword\">NOT</span> <span class=\"keyword\">NULL</span> <span class=\"keyword\">DEFAULT</span> <span class=\"number\">0</span>,</span><br><span class=\"line\"> <span class=\"type\">timestamp</span> TEXT <span class=\"keyword\">NOT</span> <span class=\"keyword\">NULL</span></span><br><span class=\"line\">);</span><br><span class=\"line\"></span><br><span class=\"line\">selectAll:</span><br><span class=\"line\"><span class=\"keyword\">SELECT</span> <span class=\"operator\">*</span> <span class=\"keyword\">FROM</span> Weather;</span><br><span class=\"line\"></span><br><span class=\"line\">selectById:</span><br><span class=\"line\"><span class=\"keyword\">SELECT</span> <span class=\"operator\">*</span> <span class=\"keyword\">FROM</span> Weather <span class=\"keyword\">WHERE</span> id <span class=\"operator\">=</span> ?;</span><br><span class=\"line\"></span><br><span class=\"line\">selectByName:</span><br><span class=\"line\"><span class=\"keyword\">SELECT</span> <span class=\"operator\">*</span> <span class=\"keyword\">FROM</span> Weather <span class=\"keyword\">WHERE</span> name <span class=\"operator\">=</span> ?;</span><br><span class=\"line\"></span><br><span class=\"line\">insertWeather:</span><br><span class=\"line\"><span class=\"keyword\">INSERT</span> <span class=\"keyword\">OR</span> REPLACE <span class=\"keyword\">INTO</span> Weather(id, name, latest_temp, <span class=\"type\">timestamp</span>)</span><br><span class=\"line\"><span class=\"keyword\">VALUES</span> (?,?,?,?);</span><br><span class=\"line\"></span><br><span class=\"line\">deleteAll:</span><br><span class=\"line\"><span class=\"keyword\">DELETE</span> <span class=\"keyword\">FROM</span> Weather;</span><br></pre></td></tr></table></figure>\n\n<h3 id=\"Drivers\"><a href=\"#Drivers\" class=\"headerlink\" title=\"Drivers\"></a>Drivers</h3><p>Implement the platform driver factories to create the database on both Android and iOS platforms. Include the following inside <strong>common/src/commonMain/kotlin/</strong> source set:</p>\n<figure class=\"highlight kotlin\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br><span class=\"line\">8</span><br><span class=\"line\">9</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">expect</span> <span class=\"class\"><span class=\"keyword\">class</span> <span class=\"title\">KmpDriverFactory</span> </span>{</span><br><span class=\"line\"> <span class=\"function\"><span class=\"keyword\">fun</span> <span class=\"title\">createDriver</span><span class=\"params\">()</span></span>: SqlDriver</span><br><span class=\"line\">}</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"function\"><span class=\"keyword\">fun</span> <span class=\"title\">createDb</span><span class=\"params\">(kmpDriverFactory: <span class=\"type\">KmpDriverFactory</span>)</span></span>: KmpDb {</span><br><span class=\"line\"> <span class=\"keyword\">val</span> driver = kmpDriverFactory.createDriver()</span><br><span class=\"line\"></span><br><span class=\"line\"> <span class=\"keyword\">return</span> KmpDb(driver)</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n\n<p>Implement the Android <code>actual</code> in <strong>common/src/AndroidMain/kotlin</strong>:</p>\n<figure class=\"highlight kotlin\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">actual</span> <span class=\"class\"><span class=\"keyword\">class</span> <span class=\"title\">KmpDriverFactory</span></span>(<span class=\"keyword\">private</span> <span class=\"keyword\">val</span> appContext: Context) {</span><br><span class=\"line\"> <span class=\"keyword\">actual</span> <span class=\"function\"><span class=\"keyword\">fun</span> <span class=\"title\">createDriver</span><span class=\"params\">()</span></span>: SqlDriver {</span><br><span class=\"line\"> <span class=\"keyword\">return</span> AndroidSqliteDriver(KmpDb.Schema, appContext, <span class=\"string\">"kmp.db"</span>)</span><br><span class=\"line\"> }</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n\n<p>Implement the iOS <code>actual</code> in <strong>common/src/iosMain/kotlin</strong>:</p>\n<figure class=\"highlight kotlin\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">actual</span> <span class=\"class\"><span class=\"keyword\">class</span> <span class=\"title\">KmpDriverFactory</span> </span>{</span><br><span class=\"line\"> <span class=\"keyword\">actual</span> <span class=\"function\"><span class=\"keyword\">fun</span> <span class=\"title\">createDriver</span><span class=\"params\">()</span></span>: SqlDriver {</span><br><span class=\"line\"> <span class=\"keyword\">return</span> NativeSqliteDriver(KmpDb.Schema, <span class=\"string\">"kmp.db"</span>)</span><br><span class=\"line\"> }</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n\n<h3 id=\"Client-access\"><a href=\"#Client-access\" class=\"headerlink\" title=\"Client access\"></a>Client access</h3><p>In order to show interacting with the SQLite database we add code in <code>MainActivity</code>, which is not where this would typically go but it validates the process. The <code>insertWeather</code> and <code>selectAll</code> methods are generated for us.</p>\n<figure class=\"highlight kotlin\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br><span class=\"line\">8</span><br><span class=\"line\">9</span><br><span class=\"line\">10</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">val</span> driver = KmpDriverFactory(<span class=\"keyword\">this</span>)</span><br><span class=\"line\"><span class=\"keyword\">val</span> db = createDb(driver)</span><br><span class=\"line\"><span class=\"keyword\">val</span> kmpQuery = db.kmpModelQueries</span><br><span class=\"line\"></span><br><span class=\"line\">kmpQuery.insertWeather(id, name, temp.toDouble(), getDate())</span><br><span class=\"line\"><span class=\"keyword\">val</span> results = kmpQuery.selectAll().executeAsList()</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"keyword\">for</span> (result <span class=\"keyword\">in</span> results) {</span><br><span class=\"line\"> <span class=\"comment\">// do something with result</span></span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n\n<p>Swift code for iOS can be used to call Kotlin <code>KmpDriverFactory()</code> and <code>createDb</code> to create the database and validate the process on iOS.</p>\n<figure class=\"highlight swift\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br><span class=\"line\">8</span><br><span class=\"line\">9</span><br><span class=\"line\">10</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">let</span> driver <span class=\"operator\">=</span> <span class=\"type\">KmpDriverFactory</span>()</span><br><span class=\"line\"><span class=\"keyword\">let</span> db <span class=\"operator\">=</span> <span class=\"type\">KmpDriverFactoryKt</span>.createDb(kmpDriverFactory: driver)</span><br><span class=\"line\"><span class=\"keyword\">let</span> kmpQuery <span class=\"operator\">=</span> db.kmpModelQueries</span><br><span class=\"line\"></span><br><span class=\"line\">kmpQuery.insertWeather(id: <span class=\"type\">KotlinLong</span>(value: id), name: name, latest_temp: <span class=\"type\">Double</span>(temp), timestamp: <span class=\"type\">DateUtilsKt</span>.getDate())</span><br><span class=\"line\"><span class=\"keyword\">let</span> results <span class=\"operator\">=</span> kmpQuery.selectAll().executeAsList()</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"keyword\">for</span> result <span class=\"keyword\">in</span> results {</span><br><span class=\"line\"> <span class=\"comment\">// do something with result</span></span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n\n<p>And that’s it for a very basic implementation, check out this <a href=\"https://github.com/doneill/kotlin-multiplatform-template/commit/0e8e69e6c486379811e7affd06986ab2ac7c98c6\">commit</a> for reference.</p>\n<h2 id=\"Resources\"><a href=\"#Resources\" class=\"headerlink\" title=\"Resources\"></a>Resources</h2><p>SQLDelight IntelliJ PLugin is available from within Android Studio by navigating to <strong>Preferences > Plugins > Marketplace > Search for SQLDelight</strong></p>\n<ul>\n<li><a href=\"https://johnoreilly.dev/posts/sqldelight-multiplatform/\">Introduction to Multiplatform Persistence with SQLDelight</a></li>\n<li><a href=\"https://handstandsam.com/2019/08/23/sqldelight-1-x-quick-start-guide-for-android/\">SQLDelight 1.x Quick Start Guide for Android</a></li>\n<li><a href=\"https://www.youtube.com/watch?v=WkIry790PHI\">A Multiplatform Delight</a></li>\n</ul>\n","site":{"data":{"projects":[{"name":"koop-provider-pg","url":"https://www.npmjs.com/package/koop-provider-pg","desc":"Koop provider to query PostGIS spatial data"},{"name":"Map Preview","url":"https://packagecontrol.io/packages/MapPreview","desc":"Preview map layer files in your web browser using Sublime Text 3"},{"name":"kotlin mulitplatform template","url":"https://github.com/doneill/kotlin-multiplatform-template","desc":"Kotlin MPP template to bootstrap Mobile/Web"}]}},"excerpt":"","more":"<h2 id=\"SQLDelight\"><a href=\"#SQLDelight\" class=\"headerlink\" title=\"SQLDelight\"></a>SQLDelight</h2><p><a href=\"https://cashapp.github.io/sqldelight/\">SQLDelight</a> provides libraries and drivers to generate typesafe Kotlin classes from SQL statements that verifies database schemas, statements, and migrations at compile time. This post will step through the basics of getting started with SQLDelight for Android and iOS using the <a href=\"https://github.com/doneill/kotlin-multiplatform-template\">kotlin-mulitplatform-template</a> I referenced in a previous <a href=\"http://gh.jdoneill.com/2019/12/06/kotlin-mulitplatform/\">post</a>, a project that started out as a template for getting your development environment started with Kotlin Multiplatform.</p>\n<h2 id=\"Dependency-setup\"><a href=\"#Dependency-setup\" class=\"headerlink\" title=\"Dependency setup\"></a>Dependency setup</h2><p>First we will get started adding the most basic dependencies, adding the plugin classpath to the project, the common library dependencies, and Android app dependencies.</p>\n<figure class=\"highlight kotlin\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br><span class=\"line\">8</span><br><span class=\"line\">9</span><br><span class=\"line\">10</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">buildscript {</span><br><span class=\"line\"> repositories {</span><br><span class=\"line\"> google()</span><br><span class=\"line\"> mavenCentral()</span><br><span class=\"line\"> jcenter()</span><br><span class=\"line\"> }</span><br><span class=\"line\"> dependencies {</span><br><span class=\"line\"> classpath(SqlDelight.GRADLE)</span><br><span class=\"line\"> }</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n\n<p>In your common gradle build file apply the plugin and add platform dependencies.</p>\n<figure class=\"highlight kotlin\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br><span class=\"line\">8</span><br><span class=\"line\">9</span><br><span class=\"line\">10</span><br><span class=\"line\">11</span><br><span class=\"line\">12</span><br><span class=\"line\">13</span><br><span class=\"line\">14</span><br><span class=\"line\">15</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"comment\">// apply plugin</span></span><br><span class=\"line\">plugins {</span><br><span class=\"line\"> ...</span><br><span class=\"line\"> id(<span class=\"string\">"com.squareup.sqldelight"</span>)</span><br><span class=\"line\">}</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"comment\">// commonMain dependency</span></span><br><span class=\"line\">implementation(<span class=\"string\">"com.squareup.sqldelight:runtime:<span class=\"subst\">${Versions.SQLDELIGHT}</span>"</span>)</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"comment\">// androidMain dependency</span></span><br><span class=\"line\">implementation(<span class=\"string\">"com.squareup.sqldelight:android-driver:<span class=\"subst\">${Versions.SQLDELIGHT}</span>"</span>)</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"comment\">// iosMain dependency</span></span><br><span class=\"line\">implementation(<span class=\"string\">"com.squareup.sqldelight:native-driver:<span class=\"subst\">${Versions.SQLDELIGHT}</span>"</span>)</span><br><span class=\"line\"></span><br></pre></td></tr></table></figure>\n\n<p>In your Android app gradle build file.</p>\n<figure class=\"highlight kotlin\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">implementation (com.squareup.sqldelight:android-driver:${Versions.SQLDELIGHT})</span><br></pre></td></tr></table></figure>\n\n<h3 id=\"Databse-schema-setup\"><a href=\"#Databse-schema-setup\" class=\"headerlink\" title=\"Databse schema setup\"></a>Databse schema setup</h3><p>Setup your database schema dependencies. Generically this looks for a database named <code>MyDatabase</code> in <code>com.example.package</code> package directory structure. More on this in the next section.</p>\n<figure class=\"highlight kotlin\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">sqldelight {</span><br><span class=\"line\"> database(<span class=\"string\">"MyDatabase"</span>) {</span><br><span class=\"line\"> packageName = <span class=\"string\">"com.example.package"</span></span><br><span class=\"line\"> sourceFolders = listOf(<span class=\"string\">"sqldelight"</span>)</span><br><span class=\"line\"> }</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n\n<p>And that’s it for a very basic setup, check out this <a href=\"https://github.com/doneill/kotlin-multiplatform-template/commit/ae5974a63a0018a26e8a89040091fb4cafb8af9a\">commit</a> for reference.</p>\n<h2 id=\"Implementation\"><a href=\"#Implementation\" class=\"headerlink\" title=\"Implementation\"></a>Implementation</h2><p>SQLDelight generates Kotlin source files which can be used to create and interact with the database. Create your initial database schema with a <code>*.sq</code> file. This file always represents the latest schema for an empty database. You need to create the SQL source file in the package defined above in the <code>commonMain</code> sourceset, e.g. <strong>common/src/commonMain/sqldelight/com/example/package</strong>. In this example we will be persisting some weather station data.</p>\n<figure class=\"highlight sql\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br><span class=\"line\">8</span><br><span class=\"line\">9</span><br><span class=\"line\">10</span><br><span class=\"line\">11</span><br><span class=\"line\">12</span><br><span class=\"line\">13</span><br><span class=\"line\">14</span><br><span class=\"line\">15</span><br><span class=\"line\">16</span><br><span class=\"line\">17</span><br><span class=\"line\">18</span><br><span class=\"line\">19</span><br><span class=\"line\">20</span><br><span class=\"line\">21</span><br><span class=\"line\">22</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">CREATE</span> <span class=\"keyword\">TABLE</span> Weather (</span><br><span class=\"line\"> id <span class=\"type\">INTEGER</span> <span class=\"keyword\">NOT</span> <span class=\"keyword\">NULL</span> <span class=\"keyword\">PRIMARY</span> KEY,</span><br><span class=\"line\"> name TEXT <span class=\"keyword\">NOT</span> <span class=\"keyword\">NULL</span>,</span><br><span class=\"line\"> latest_temp <span class=\"type\">REAL</span> <span class=\"keyword\">NOT</span> <span class=\"keyword\">NULL</span> <span class=\"keyword\">DEFAULT</span> <span class=\"number\">0</span>,</span><br><span class=\"line\"> <span class=\"type\">timestamp</span> TEXT <span class=\"keyword\">NOT</span> <span class=\"keyword\">NULL</span></span><br><span class=\"line\">);</span><br><span class=\"line\"></span><br><span class=\"line\">selectAll:</span><br><span class=\"line\"><span class=\"keyword\">SELECT</span> <span class=\"operator\">*</span> <span class=\"keyword\">FROM</span> Weather;</span><br><span class=\"line\"></span><br><span class=\"line\">selectById:</span><br><span class=\"line\"><span class=\"keyword\">SELECT</span> <span class=\"operator\">*</span> <span class=\"keyword\">FROM</span> Weather <span class=\"keyword\">WHERE</span> id <span class=\"operator\">=</span> ?;</span><br><span class=\"line\"></span><br><span class=\"line\">selectByName:</span><br><span class=\"line\"><span class=\"keyword\">SELECT</span> <span class=\"operator\">*</span> <span class=\"keyword\">FROM</span> Weather <span class=\"keyword\">WHERE</span> name <span class=\"operator\">=</span> ?;</span><br><span class=\"line\"></span><br><span class=\"line\">insertWeather:</span><br><span class=\"line\"><span class=\"keyword\">INSERT</span> <span class=\"keyword\">OR</span> REPLACE <span class=\"keyword\">INTO</span> Weather(id, name, latest_temp, <span class=\"type\">timestamp</span>)</span><br><span class=\"line\"><span class=\"keyword\">VALUES</span> (?,?,?,?);</span><br><span class=\"line\"></span><br><span class=\"line\">deleteAll:</span><br><span class=\"line\"><span class=\"keyword\">DELETE</span> <span class=\"keyword\">FROM</span> Weather;</span><br></pre></td></tr></table></figure>\n\n<h3 id=\"Drivers\"><a href=\"#Drivers\" class=\"headerlink\" title=\"Drivers\"></a>Drivers</h3><p>Implement the platform driver factories to create the database on both Android and iOS platforms. Include the following inside <strong>common/src/commonMain/kotlin/</strong> source set:</p>\n<figure class=\"highlight kotlin\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br><span class=\"line\">8</span><br><span class=\"line\">9</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">expect</span> <span class=\"class\"><span class=\"keyword\">class</span> <span class=\"title\">KmpDriverFactory</span> </span>{</span><br><span class=\"line\"> <span class=\"function\"><span class=\"keyword\">fun</span> <span class=\"title\">createDriver</span><span class=\"params\">()</span></span>: SqlDriver</span><br><span class=\"line\">}</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"function\"><span class=\"keyword\">fun</span> <span class=\"title\">createDb</span><span class=\"params\">(kmpDriverFactory: <span class=\"type\">KmpDriverFactory</span>)</span></span>: KmpDb {</span><br><span class=\"line\"> <span class=\"keyword\">val</span> driver = kmpDriverFactory.createDriver()</span><br><span class=\"line\"></span><br><span class=\"line\"> <span class=\"keyword\">return</span> KmpDb(driver)</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n\n<p>Implement the Android <code>actual</code> in <strong>common/src/AndroidMain/kotlin</strong>:</p>\n<figure class=\"highlight kotlin\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">actual</span> <span class=\"class\"><span class=\"keyword\">class</span> <span class=\"title\">KmpDriverFactory</span></span>(<span class=\"keyword\">private</span> <span class=\"keyword\">val</span> appContext: Context) {</span><br><span class=\"line\"> <span class=\"keyword\">actual</span> <span class=\"function\"><span class=\"keyword\">fun</span> <span class=\"title\">createDriver</span><span class=\"params\">()</span></span>: SqlDriver {</span><br><span class=\"line\"> <span class=\"keyword\">return</span> AndroidSqliteDriver(KmpDb.Schema, appContext, <span class=\"string\">"kmp.db"</span>)</span><br><span class=\"line\"> }</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n\n<p>Implement the iOS <code>actual</code> in <strong>common/src/iosMain/kotlin</strong>:</p>\n<figure class=\"highlight kotlin\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">actual</span> <span class=\"class\"><span class=\"keyword\">class</span> <span class=\"title\">KmpDriverFactory</span> </span>{</span><br><span class=\"line\"> <span class=\"keyword\">actual</span> <span class=\"function\"><span class=\"keyword\">fun</span> <span class=\"title\">createDriver</span><span class=\"params\">()</span></span>: SqlDriver {</span><br><span class=\"line\"> <span class=\"keyword\">return</span> NativeSqliteDriver(KmpDb.Schema, <span class=\"string\">"kmp.db"</span>)</span><br><span class=\"line\"> }</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n\n<h3 id=\"Client-access\"><a href=\"#Client-access\" class=\"headerlink\" title=\"Client access\"></a>Client access</h3><p>In order to show interacting with the SQLite database we add code in <code>MainActivity</code>, which is not where this would typically go but it validates the process. The <code>insertWeather</code> and <code>selectAll</code> methods are generated for us.</p>\n<figure class=\"highlight kotlin\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br><span class=\"line\">8</span><br><span class=\"line\">9</span><br><span class=\"line\">10</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">val</span> driver = KmpDriverFactory(<span class=\"keyword\">this</span>)</span><br><span class=\"line\"><span class=\"keyword\">val</span> db = createDb(driver)</span><br><span class=\"line\"><span class=\"keyword\">val</span> kmpQuery = db.kmpModelQueries</span><br><span class=\"line\"></span><br><span class=\"line\">kmpQuery.insertWeather(id, name, temp.toDouble(), getDate())</span><br><span class=\"line\"><span class=\"keyword\">val</span> results = kmpQuery.selectAll().executeAsList()</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"keyword\">for</span> (result <span class=\"keyword\">in</span> results) {</span><br><span class=\"line\"> <span class=\"comment\">// do something with result</span></span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n\n<p>Swift code for iOS can be used to call Kotlin <code>KmpDriverFactory()</code> and <code>createDb</code> to create the database and validate the process on iOS.</p>\n<figure class=\"highlight swift\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br><span class=\"line\">8</span><br><span class=\"line\">9</span><br><span class=\"line\">10</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">let</span> driver <span class=\"operator\">=</span> <span class=\"type\">KmpDriverFactory</span>()</span><br><span class=\"line\"><span class=\"keyword\">let</span> db <span class=\"operator\">=</span> <span class=\"type\">KmpDriverFactoryKt</span>.createDb(kmpDriverFactory: driver)</span><br><span class=\"line\"><span class=\"keyword\">let</span> kmpQuery <span class=\"operator\">=</span> db.kmpModelQueries</span><br><span class=\"line\"></span><br><span class=\"line\">kmpQuery.insertWeather(id: <span class=\"type\">KotlinLong</span>(value: id), name: name, latest_temp: <span class=\"type\">Double</span>(temp), timestamp: <span class=\"type\">DateUtilsKt</span>.getDate())</span><br><span class=\"line\"><span class=\"keyword\">let</span> results <span class=\"operator\">=</span> kmpQuery.selectAll().executeAsList()</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"keyword\">for</span> result <span class=\"keyword\">in</span> results {</span><br><span class=\"line\"> <span class=\"comment\">// do something with result</span></span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n\n<p>And that’s it for a very basic implementation, check out this <a href=\"https://github.com/doneill/kotlin-multiplatform-template/commit/0e8e69e6c486379811e7affd06986ab2ac7c98c6\">commit</a> for reference.</p>\n<h2 id=\"Resources\"><a href=\"#Resources\" class=\"headerlink\" title=\"Resources\"></a>Resources</h2><p>SQLDelight IntelliJ PLugin is available from within Android Studio by navigating to <strong>Preferences > Plugins > Marketplace > Search for SQLDelight</strong></p>\n<ul>\n<li><a href=\"https://johnoreilly.dev/posts/sqldelight-multiplatform/\">Introduction to Multiplatform Persistence with SQLDelight</a></li>\n<li><a href=\"https://handstandsam.com/2019/08/23/sqldelight-1-x-quick-start-guide-for-android/\">SQLDelight 1.x Quick Start Guide for Android</a></li>\n<li><a href=\"https://www.youtube.com/watch?v=WkIry790PHI\">A Multiplatform Delight</a></li>\n</ul>\n"}],"PostAsset":[{"_id":"source/_posts/mappreview/map-preview.png","slug":"map-preview.png","post":"ckz0jkmcw0008nvhwb5a12p26","modified":0,"renderable":0},{"_id":"source/_posts/koop-vector-tile/wa-boat-launch.png","slug":"wa-boat-launch.png","post":"ckz0jkmd0000jnvhw4xdndfgw","modified":0,"renderable":0},{"_id":"source/_posts/pgkoop/agol.png","slug":"agol.png","post":"ckz0jkmd0000hnvhwd584gwmz","modified":0,"renderable":0},{"_id":"source/_posts/pgkoop/leaflet.png","slug":"leaflet.png","post":"ckz0jkmd0000hnvhwd584gwmz","modified":0,"renderable":0},{"_id":"source/_posts/pgkoop/qgis.png","slug":"qgis.png","post":"ckz0jkmd0000hnvhwd584gwmz","modified":0,"renderable":0},{"_id":"source/_posts/kotlin-cli/app-structure.png","slug":"app-structure.png","post":"ckz0jkmd1000knvhw820s48gp","modified":0,"renderable":0}],"PostCategory":[],"PostTag":[{"post_id":"ckz0jkmcw0008nvhwb5a12p26","tag_id":"ckz0jkmcy000bnvhwc6yg4luw","_id":"ckz0jkmd1000qnvhw8jdtfotz"},{"post_id":"ckz0jkmcw0008nvhwb5a12p26","tag_id":"ckz0jkmd0000invhwcwqy87ay","_id":"ckz0jkmd1000rnvhw1m3r1rne"},{"post_id":"ckz0jkmcw0008nvhwb5a12p26","tag_id":"ckz0jkmd1000lnvhw8j0tfkxr","_id":"ckz0jkmd2000tnvhw7msrcelh"},{"post_id":"ckz0jkmcw0008nvhwb5a12p26","tag_id":"ckz0jkmd1000nnvhwh82ig2m1","_id":"ckz0jkmd2000unvhw8volfe0u"},{"post_id":"ckz0jkmcw0008nvhwb5a12p26","tag_id":"ckz0jkmd1000onvhw9f8lgjok","_id":"ckz0jkmd2000wnvhw72ng4j9d"},{"post_id":"ckz0jkmcx000anvhwhoexbyym","tag_id":"ckz0jkmd1000pnvhwg55mclmj","_id":"ckz0jkmd2000xnvhwedrg9bqb"},{"post_id":"ckz0jkmcx000anvhwhoexbyym","tag_id":"ckz0jkmd1000snvhwbov6fddw","_id":"ckz0jkmd2000znvhw067vcvsy"},{"post_id":"ckz0jkmcz000dnvhwbp6s8hoj","tag_id":"ckz0jkmd2000vnvhw76lk26ol","_id":"ckz0jkmd30012nvhwdhvn9tj8"},{"post_id":"ckz0jkmcz000dnvhwbp6s8hoj","tag_id":"ckz0jkmd2000ynvhwhsxzdjor","_id":"ckz0jkmd30013nvhwevxi5e1y"},{"post_id":"ckz0jkmcz000dnvhwbp6s8hoj","tag_id":"ckz0jkmd1000nnvhwh82ig2m1","_id":"ckz0jkmd30015nvhw2c3a8qrv"},{"post_id":"ckz0jkmcz000fnvhwh3mweasz","tag_id":"ckz0jkmd20011nvhw48l61bso","_id":"ckz0jkmd30017nvhw83vufl4f"},{"post_id":"ckz0jkmcz000fnvhwh3mweasz","tag_id":"ckz0jkmd30014nvhwaqcqa2ja","_id":"ckz0jkmd30018nvhw4m0081db"},{"post_id":"ckz0jkmd0000hnvhwd584gwmz","tag_id":"ckz0jkmd2000vnvhw76lk26ol","_id":"ckz0jkmd3001fnvhwefifaclc"},{"post_id":"ckz0jkmd0000hnvhwd584gwmz","tag_id":"ckz0jkmd30019nvhwazoybsm7","_id":"ckz0jkmd3001gnvhw1t8c0ola"},{"post_id":"ckz0jkmd0000hnvhwd584gwmz","tag_id":"ckz0jkmd3001anvhwb62038lz","_id":"ckz0jkmd4001invhw7n5hc3sc"},{"post_id":"ckz0jkmd0000hnvhwd584gwmz","tag_id":"ckz0jkmd1000nnvhwh82ig2m1","_id":"ckz0jkmd4001jnvhw7hxv64ng"},{"post_id":"ckz0jkmd0000hnvhwd584gwmz","tag_id":"ckz0jkmd3001cnvhwa6az3822","_id":"ckz0jkmd4001lnvhw51kw32vl"},{"post_id":"ckz0jkmd0000hnvhwd584gwmz","tag_id":"ckz0jkmd1000onvhw9f8lgjok","_id":"ckz0jkmd4001mnvhw5viw4pcv"},{"post_id":"ckz0jkmd0000jnvhw4xdndfgw","tag_id":"ckz0jkmd2000vnvhw76lk26ol","_id":"ckz0jkmd4001qnvhw7c2f06gk"},{"post_id":"ckz0jkmd0000jnvhw4xdndfgw","tag_id":"ckz0jkmd3001hnvhw9ja98gxl","_id":"ckz0jkmd4001rnvhw6mp28fuv"},{"post_id":"ckz0jkmd0000jnvhw4xdndfgw","tag_id":"ckz0jkmd3001anvhwb62038lz","_id":"ckz0jkmd4001tnvhwdofj57ao"},{"post_id":"ckz0jkmd0000jnvhw4xdndfgw","tag_id":"ckz0jkmd1000nnvhwh82ig2m1","_id":"ckz0jkmd4001unvhw8lbu949g"},{"post_id":"ckz0jkmd0000jnvhw4xdndfgw","tag_id":"ckz0jkmd3001cnvhwa6az3822","_id":"ckz0jkmd4001wnvhw8vwb3i39"},{"post_id":"ckz0jkmd1000knvhw820s48gp","tag_id":"ckz0jkmd1000pnvhwg55mclmj","_id":"ckz0jkmd4001ynvhwfnj1diqx"},{"post_id":"ckz0jkmd1000knvhw820s48gp","tag_id":"ckz0jkmd4001snvhwbtr3d1qw","_id":"ckz0jkmd4001znvhwbyymflzk"},{"post_id":"ckz0jkmd1000knvhw820s48gp","tag_id":"ckz0jkmd4001vnvhwdj895y5v","_id":"ckz0jkmd40021nvhwgovx2pn8"},{"post_id":"ckz0jkmd1000mnvhw3s5q42jg","tag_id":"ckz0jkmd1000pnvhwg55mclmj","_id":"ckz0jkmd40022nvhw5ye62m9h"},{"post_id":"ckz0jkmd1000mnvhw3s5q42jg","tag_id":"ckz0jkmd1000snvhwbov6fddw","_id":"ckz0jkmd40023nvhwd7qydj8k"}],"Tag":[{"name":"sublime text","_id":"ckz0jkmcy000bnvhwc6yg4luw"},{"name":"python","_id":"ckz0jkmd0000invhwcwqy87ay"},{"name":"topojson","_id":"ckz0jkmd1000lnvhw8j0tfkxr"},{"name":"geojson","_id":"ckz0jkmd1000nnvhwh82ig2m1"},{"name":"leaflet","_id":"ckz0jkmd1000onvhw9f8lgjok"},{"name":"kotlin","_id":"ckz0jkmd1000pnvhwg55mclmj"},{"name":"multiplatform","_id":"ckz0jkmd1000snvhwbov6fddw"},{"name":"esri","_id":"ckz0jkmd2000vnvhw76lk26ol"},{"name":"arcgis rest","_id":"ckz0jkmd2000ynvhwhsxzdjor"},{"name":"arcgis runtime","_id":"ckz0jkmd20011nvhw48l61bso"},{"name":"google maps","_id":"ckz0jkmd30014nvhwaqcqa2ja"},{"name":"geoservices","_id":"ckz0jkmd30019nvhwazoybsm7"},{"name":"arcgis","_id":"ckz0jkmd3001anvhwb62038lz"},{"name":"postgis","_id":"ckz0jkmd3001cnvhwa6az3822"},{"name":"vector tiles","_id":"ckz0jkmd3001hnvhw9ja98gxl"},{"name":"clikt","_id":"ckz0jkmd4001snvhwbtr3d1qw"},{"name":"coroutines","_id":"ckz0jkmd4001vnvhwdj895y5v"}]}}