{"id":13907533,"url":"https://github.com/reyemtm/pwa-maps","last_synced_at":"2025-03-25T12:33:14.131Z","repository":{"id":44114153,"uuid":"173929437","full_name":"reyemtm/pwa-maps","owner":"reyemtm","description":"A presentation on using OpenMapTiles and service workers to turn an interactive map into an offline-capable Progressive Web App.","archived":false,"fork":false,"pushed_at":"2022-12-09T14:47:23.000Z","size":5207,"stargazers_count":69,"open_issues_count":9,"forks_count":17,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-03-20T08:40:37.380Z","etag":null,"topics":["mapbox","netlify","pwas","service-worker"],"latest_commit_sha":null,"homepage":"https://pwa-trails.netlify.app/#13/39.5933/-82.58844","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/reyemtm.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2019-03-05T10:55:27.000Z","updated_at":"2025-01-20T14:26:58.000Z","dependencies_parsed_at":"2023-01-25T22:15:17.350Z","dependency_job_id":null,"html_url":"https://github.com/reyemtm/pwa-maps","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/reyemtm%2Fpwa-maps","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/reyemtm%2Fpwa-maps/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/reyemtm%2Fpwa-maps/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/reyemtm%2Fpwa-maps/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/reyemtm","download_url":"https://codeload.github.com/reyemtm/pwa-maps/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":245463023,"owners_count":20619598,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["mapbox","netlify","pwas","service-worker"],"created_at":"2024-08-06T23:01:58.870Z","updated_at":"2025-03-25T12:33:11.604Z","avatar_url":"https://github.com/reyemtm.png","language":"JavaScript","funding_links":[],"categories":["JavaScript"],"sub_categories":[],"readme":"## Progressive \u003cbr\u003eWeb ~~Apps~~ Maps (PWA)\n\nMalcolm Meyer\n\nGIS Specialist | \n[City of Zanesville](https://gis.coz.org)\n\nNotes: I have been at the City of Zanesville for about a year, before that I had a host of other GIS positions and have been in the GIS field for almost ten years. Also, I am not a developer, but I do play one at work.\n\n## Introductions\n\nYour GIS origin story in two minutes or less...\n\nNotes: I graduated from the College of Wooster with a degree in Sociology and Urban Planning, and after several years in AmeriCorps I decided to go back to school to get my master's degree in International Affairs. I took several geography courses in graduate school at Ohio University, and that is where I got interested in GIS.\n\n## Goals\n\u003cul style=\"none;\"\u003e\n\u003ch4\u003e\u003cimg src=\"presentation/checkbox-blank.svg\" \u003eQuick Overview of PWAs\u003c/h4\u003e\n\u003ch4\u003e\u003cimg src=\"presentation/checkbox-blank.svg\" \u003eCreate a Basic Web Map\u003c/h4\u003e\n\u003ch4\u003e\u003cimg src=\"presentation/checkbox-blank.svg\" \u003eTurn this Map into a PWA\u003c/h4\u003e\n\u003ch4\u003e\u003cimg src=\"presentation/checkbox-blank.svg\" \u003eMake the PWA Installable\u003c/h4\u003e\n\u003ch4\u003e\u003cimg src=\"presentation/checkbox-blank.svg\" \u003eAllow the PWA to be used Offline\u003c/h4\u003e\n\u003ch4\u003e\u003cimg src=\"presentation/checkbox-blank.svg\" \u003eHost \u0026 Install the PWA\u003c/h4\u003e\n\u003c/ul\u003e\n\n## Time Allowing\n\n\u003cul style=\"none;\"\u003e\n\u003ch4\u003e\u003cimg src=\"presentation/checkbox-blank.svg\" \u003eAdd a Vector Tiles Basemap\u003c/h4\u003e\n\u003ch4\u003e\u003cimg src=\"presentation/checkbox-blank.svg\" \u003eAdd Overlay Layers\u003c/h4\u003e\n\u003c/ul\u003e\n\n## What is a PWA\n\u003e Set of components to allow a website to behave more like a native application\n\nNotes: there are many examples of PWAs in the wild and we will look at some of those later, but it is important to note that as with all web technologies, browser support is varied and the tools to work with PWAs are rapidly changing.\n\n## Benefits\nNative App Behaviors\n\nCustom App Colors\n\nCaching \u0026 Offline Support\n\n*Push Notifications\n\nNotes: We will not cover push notifications in this presentation, as that requires a separate server or service. There is a wealth of information online if you are interested in going further with PWAs.\n\n## Multi-Platform\nAndroid\n\nChrome\n\nChromebooks\n\niOS\n\nWindows Store\n\n---\n\n\u003ciframe src=\"https://www.pwastats.com\" width=\"100%\" height=\"500px\"\u003e\u003c/iframe\u003e\n\nNotes: PWA Stats highlights some popular PWAs. Before we get into building our own PWA, let's look at how the Chrome Dev Tools can be used to test out PWA functionality using the website petlove.com.br.\n\n## Petlove PWA\n\n![](presentation/petlove.png)\n\n## Why a PWA Map?\n\n* Cached Assets for Landing Pages \u0026 Map UI\n* Offline Maps for Field Use - No App Required\n* User Convenience - App Drawer/Homescreen\n\nNotes: So why would we want to bring this PWA functionality to web maps? First, it can give us more control over the caching of static assets like css and javascript in our maps or map portals. Also it can allow us to create installable applications without the user needing to download a separate app. Now let's look at how the sample PWA map I built for this workshop looks and take a look at the install process.\n\n## Example PWA Map\n[https://pwa-trails.netlify.com](https://pwa-trails.netlify.com)\n\n\u003ciframe src=\"https://pwa-trails.netlify.com\" width=\"100%\" height=\"400px\"\u003e\u003c/iframe\u003e\n\nNotes: This is a very basic web map, showing the various trails in one of the Franklin County MetroParks. What's different about this map is that it is a fully functional Progressive Web App. It can be installed on any device and used completely offline. The park already has paper maps, but of course a paper map does not give you the ability to show your GPS position while you're hiking the trails. Also, this area has very bad cell service, so having a map that can work offline is very important. I was actually a park ranger here for a short time, and in those two years we did have someone get lost on the trail. If they would have had a map such as this, it might have been easier for them to find a way out on their own. Now let's look at the install process for this app.\n\n---\n\n![](presentation/pwa-install-1.jpg)\n\nNotes: This is how the install process looks on an Android device. To get the install prompt on other devices you will have to write some additional JavaScript code.\n\n---\n\n![](presentation/pwa-install-2.jpg)\n\n---\n\n![](presentation/pwa-app-drawer.jpg)\n\nNotes: Here you can see the app is actually listed in the app drawer in addition to the homescreen. This can make it a lot easier for users to get to your application.\n\n---\n\n![](presentation/pwa-install-windows.png)\n\nNotes: This is how you would go about installing this app in windows from Chrome.\n\n---\n\n![](presentation/pwa-offline.jpg)\n\nNotes: Here you can see that, as outlined in red, the app is completely offline, but is still usable.\n\n---\n![](presentation/pwa-example.jpg)\n\nNotes: And here is the app as it looks launched from the windows desktop as a standalone PWA.\n\n## Code Break\n\nNotes: https://www.smashingmagazine.com/2016/02/making-a-service-worker/\n\n## Step 1. Install\n\n```javascript\n/*\nhttps://github.com/reyemtm/pwa-maps/archive/master.zip\n*/\n```\n```javascript\n/*\nunzip and then open this folder in with VS Code\nCTRL + '`' to open the terminal\n*/\n```\n\n```\nnpm install\n```\n\n```\nnpm run build\n\n```\n\n```\nnpm start\n```\n\nNotes: First we need to download the GitHub repository for this project. Then we will install the dependencies. Finally we will build the very basic web map and run it through a PWA audit to see how it fares. The app will be located in the 'public' folder. Now when your app launched, if it did not launch in Chrome, please copy the url and open that url in Chrome. You should have a map that looks like this - next slide.\n\n---\n\n![](presentation/sample-app-1.png)\n\n\nNotes: We have a basic web map. This map is using Mapbox GL JS as the mapping API, and we are not going to go into depth in learning this API. The documentation online is very good and can get you started with mapping using Mapbox GL JS. So now that we have a working map, let's run it through the same PWA audit that we ran the petlove site through. When we run the map through the PWA audit, while we do get some positive results, it is missing the key features of a PWA: the manifest.json and service worker.\n\n## Step 2. Test\n```javascript\n//CTRL + SHIFT + i then Audit Tab\n```\n\n* Progressive Web App Audit in Chrome\n* Check 'Offline' in the Applcation Tab\n\nNotes: So again we are going to do the PWA Audit in Chrome as well as test the offline capability.\n\n## \u003cspan style=\"color:firebrick;\"\u003eErrors!!\u003c/span\u003e\n\n* No manifest\n* No service worker\n* App does not work offline\n\n## Goals\n\n\u003cul style=\"none;\"\u003e\n\u003ch4\u003e\u003cimg src=\"presentation/check.svg\" \u003eQuick Overview of PWAs\u003c/h4\u003e\n\u003ch4\u003e\u003cimg src=\"presentation/check.svg\" \u003eCreate a Basic Web Map\u003c/h4\u003e\n\u003ch4\u003e\u003cimg src=\"presentation/checkbox-blank.svg\" \u003eTurn this Map into a PWA\u003c/h4\u003e\n\u003ch4\u003e\u003cimg src=\"presentation/checkbox-blank.svg\" \u003eMake the PWA Installable\u003c/h4\u003e\n\u003ch4\u003e\u003cimg src=\"presentation/checkbox-blank.svg\" \u003eAllow the PWA to be used Offline\u003c/h4\u003e\n\u003ch4\u003e\u003cimg src=\"presentation/checkbox-blank.svg\" \u003eHost \u0026 Install the PWA\u003c/h4\u003e\n\u003c/ul\u003e\n\n## Let's Look at the Elements of a PWA\n- manifest.json\n- service-worker.js\n- *Mobile First Design\n- *Progressive Enhancement\n\nNotes: From the results of the audit we see that we need to add a manifest.json file and the service worker. These, along with a mobile first design and progressive enhancement, are the keys to a PWA. Since our application is a simple web map, we do not need to worry too much about the second two aspects here. We will focus on getting the core functionality of the PWA working.\n\n## manifest.json\n```javascript\n/* https://tomitm.github.io/appmanifest/ */\n```\n\n```\n{\n  \"name\": \"Clear Creek Trail Map\",\n  \"short_name\": \"CC Maps\",\n  \"scope\": \"/\",\n  \"start_url\": \"/\",\n  \"icons\": [\n    {\n      \"src\": \"/img/trails512.png\",\n      \"type\": \"image/png\",\n      \"sizes\": \"512x512\"\n    }\n  ],\n  \"background_color\": \"#d8e8c8\",\n  \"theme_color\": \"#d8e8c8\",\n  \"display\": \"standalone\"\n}\n\n```\n\nNotes: Create a manifest.json file in your public folder, and give it a name, short name and colors. If you want to look for a new icon you can, and you can use the above link to take a 512x512 image and make all the appropriate sizes for your PWA. Let's take a short break while you create this file. You can create the entire file using the url if you prefer. Just remember to copy all the assets to you public folder.\n\n## Test Again\n\n* Passes manifest tests\n* No service-worker\n* No offline support\n\n## Service workers\n\u003e ...intercept and handle network requests, including programmatically managing a cache of responses.\n\n## sw.js\n\n```javascript\n/* \nCreate an empty sw.js file in the root of 'public'.\nIn your index.html you will already see the following\ncode in the head of the document.\n*/\n```\n\n```\n\u003c!--register the service worker--\u003e\n\u003cscript\u003e\n  if ('serviceWorker' in navigator) {\n    navigator.serviceWorker\n      .register('/sw.js')\n      .then(function () {\n        console.log(\"Service Worker Registered\");\n      });\n  }\n\u003c/script\u003e\n```\n\n## \u003cspan style=\"color:#28a745;\"\u003eSuccess!\u003c/span\u003e\n* Installable\n* PWA optimized\n* Still no offline support \u003c!-- .element: class=\"fragment\" data-fragment-index=\"1\" --\u003e\n* No service worker caching \u003c!-- .element: class=\"fragment\" data-fragment-index=\"2\" --\u003e\n\n## Adding Offline Cache\n\n[https://css-tricks.com/serviceworker-for-offline/](https://css-tricks.com/serviceworker-for-offline/)\n\n```javascript\nnpm run cache \n```\n```javascript\n/*\nthis will simply copy a prepared sw.js file to the public folder \n*/\n```\n\n## Examining the Events\n\n```\n/* the cache is added after the install event */\n6 self.addEventListener(\"install\", function(event) {\n```\n```\n/* requests are fulfilled with the cache, then the network */\n38 self.addEventListener(\"fetch\", function(event) {\n```\n```javascript\n/* when a new sw.js is activated the cache is refreshed */\n141 self.addEventListener(\"activate\", function(event) {\n\n```\nNotes: take a look at the service worker file and go through the various functions\n\n## Service Worker Test\n* Installable\n* PWA optimized\n* Offline support\n* But that's not much of a map...\u003c!-- .element: class=\"fragment\" data-fragment-index=\"1\" --\u003e\n* And that's a lot of code \u003c!-- .element: class=\"fragment\" data-fragment-index=\"2\" --\u003e\n\n## Workbox\n\nAutomate the creation of the service worker\n\n```dockerfile\"\nnpm install workbox-cli # this is already installed\n```\n\nCommands\n\n```\nnpm run workbox-wizard # workbox wizard \n```\n\n```\nnpm run workbox-cache # workbox generateSW workbox-config.js\n```\n\n```\n/*\nyou could use the native cli commands if installed globally\n*/\n```\n\nNotes: I just added some simple node scripts to run the workbox-cli without needing to install it globally. Workbox replaces \nthe now deprecated sw-precache tool, also by Google. Now we can examine this new sw.js file. All the event logic is taken by an separate Google script. The sw.js file simply lists the cached assets.\n\n## Creating the Basemap\n\n* Use raw vector tile files\n* Use GeoJSON for any additional map layers\n\n## OpenMapTiles\n\n1. Download a prepared OpenMapTiles extract \n2. Create a GeoJSON to clip the extract \u003c!-- .element: class=\"fragment\" data-fragment-index=\"2\" --\u003e\n3. Install the mbtiles-extracts Node JS Package \u003c!-- .element: class=\"fragment\" data-fragment-index=\"3\" --\u003e\n4. Edit it to allow for not passing in a property name \u003c!-- .element: class=\"fragment\" data-fragment-index=\"4\" --\u003e\n5. Unpack raw vector tiles from mbtiles \u003c!-- .element: class=\"fragment\" data-fragment-index=\"5\" --\u003e\n  - https://www.npmjs.com/package/mbtiles2ungzpbf \u003c!-- .element: class=\"fragment\" data-fragment-index=\"5\" --\u003e\n6. Result is hundreds of small files which must be cached \u003c!-- .element: class=\"fragment\" data-fragment-index=\"6\" --\u003e\n\n## Extract Sizes\nCleveland Metro Area - 17 MB\n\nCuyahoga County - 6 MB\n\nCity of Columbus - 7.5 MB\n\nOVRDC Region (12 Counties) - 16 MB\n\nNotes: Is anyone familiar with vector tiles? OpenMapTiles provides tools to build custom extracts of OpenStreetMap and pre-built area extracts \n\n## Copy the Basemap\n\n```\nnpm run copy\n```\n```javascript\n/*\nthis will copy the OpenMapTiles data, the basemap style and the trails GeoJSON data to the public folder\n*/\n```\n\n## Edit index.html\n\nReplace the blank style with \"bright.json\"\n\n```javascript\nvar map = new mapboxgl.Map({\n  container: \"map\",\n  style: \"bright.json\",\n  hash: true,\n  center: [-82.58844, 39.5933],\n  zoom: 6\n});\n```\n\n```\n/* This will change your basemap, but will not add the trails layer. If we have time we will do that manually, otherwise you can work on this on your own.*/\n```\n## Publish to Netlify\n\n**Delete the ``.netlify`` folder!!**\n```\nnpm run deploy\n```\n```\n/*\nlogin\nchoose the public folder when asked\n*/\n```\n\n## Final Notes\n\nOnce your site is live, replace ``http://127.0.0.1`` in your bright.json file with the secure url of your new site\n\n```\nnpm run workbox-cache\n``` \n```\nnpm run deploy\n```\n\n## Goals\n\n\u003cul style=\"none;\"\u003e\n\u003ch4\u003e\u003cimg src=\"presentation/check.svg\" \u003eQuick Overview of PWAs\u003c/h4\u003e\n\u003ch4\u003e\u003cimg src=\"presentation/check.svg\" \u003eCreate a Basic Web Map\u003c/h4\u003e\n\u003ch4\u003e\u003cimg src=\"presentation/check.svg\" \u003eTurn this Map into a PWA\u003c/h4\u003e\n\u003ch4\u003e\u003cimg src=\"presentation/check.svg\" \u003eMake the PWA Installable\u003c/h4\u003e\n\u003ch4\u003e\u003cimg src=\"presentation/check.svg\" \u003eAllow the PWA to be used Offline\u003c/h4\u003e\n\u003ch4\u003e\u003cimg src=\"presentation/check.svg\" \u003eHost \u0026 Install the PWA\u003c/h4\u003e\n\u003c/ul\u003e\n\n## Thanks\n\n\u003ch3\u003eMalcolm Meyer\u003c/h3\u003e\n\nmalcolm.meyer@coz.org\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Freyemtm%2Fpwa-maps","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Freyemtm%2Fpwa-maps","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Freyemtm%2Fpwa-maps/lists"}