My Design System Google Maps Implementation

Introduction

I tried to create an implementation of the Google Maps JavaScript API that only requires the user of the JavaScript to include certain HTML markup on the page for the maps to work correctly. I wanted to do this to make implementing maps easier and quicker. Also, there were some problems using the API with HTMX , and I didn't have a library like React Google Maps to do most of the work of implementing the library for me, so I tried to implement it myself.

Assumptions

This implementation assumes that:

  • you want to use HTMX to create a SPA (Single Page Application),
  • you want to use the maps JavaScript API for three main purposes:
    1. to display GeoJson and markers,
    2. to accept a circle input (an input with a radius and a center (latitude and longitude)),
    3. and to accept a GeoJson polygon input.
  • and you want to use the Dynamic Library Import to load Google Maps.
    • Your CSP (Content Security Policy) allows for inline scripts with nonces.
  • The Dynamic Library Import method of loading the google maps library has caused some issues with reloading maps after page change. I am going to try to switch to the NPM js-api-loader package to see if that solves the issue.

JavaScript

Currently, the transpiled JavaScript for this implementation of the Google Maps API is 25kB. The 'main' JavaScript for the application lazy loads the JavaScript the first time a div[id^"google-map"], div[id^="circle_google_map"], or a div[id^="draw_google_map"] is encountered on the page. These maps are then added to a maps object with their ids being the keys for their representation in the object. This means that the ids for the maps should be unique per page, and they should be valid javascript variable names. The maps are then handled differently based on what the id for the div starts with:

  • div[id^="google_map"]
    • The code looks for any <span>s that have the attribute data-show-geography set to "true" and then checks the data-type of the <span>.
      • If the type is set to geojson, the JavaScript code gets the data-geojson attribute of the <span> and attempts to load it on the map.
      • If the type is set to marker, then the JavaScript code gets the data-center attribute of the <span>, which should be equal to a stringified { lat, lng } object, and adds a marker to the map at that position. The marker can have an optional data-label attribute that would be used to add a label to the marker.
    • The code fits the map to bounds defined by the outermost bounds of each data-bounds attribute for geographies in the same <section> as the map.
    • The code sets the center of the map to be the average of all data-center attributes of each Geography in the same <section> as the map.
  • div[id^="circle_google_map"]
    • Circle google map inputs are designed to get a circle input from the user - an input with a latitude and longitude center and a radius. The reasons this was included in the implementation are:
      1. Unlike polygon geography inputs, circle inputs don't have a large size (they could reasonably be included in a query string)
      2. It would be faster - if you wanted to filter something based on geometry - to query a postgres database with a Postgis extension using a circle than it would to look for an intersection based on a polygon input
    • Circle Inputs can include a data-bounds attribute which would set strict bounds for the map (as opposed to fitting the map to a certain area).
    • Circle Inputs can include a data-max-input attribute which would define the maximum number of circles that the map would allow only a certain amount of circles to be added to the map. If a data-max-input attribute is included, then a data-snackbar-el attribute should be included and it should equal a css query string that would be used to find the snackbar whenever the max number of inputs is exceeded.
    • Circle inputs can be removed from the map by double clicking the shape and confirming its removal.
  • div[id^="draw_google_map"]
    • Polygon inputs have basically the same concerns as circle inputs. I may add a data-max-length attribute check later to check for polygons that have too many latitude / longitude pairs - this could cause issues if the user inputs something too large, but I have not yet.

Example Showing Geography

Hidden Geography Markup for Tab Panel 1

Block Group 1 Of Census Tract 965700 Of Adams County, Nebraska

Lorem ipsum dolor sit, amet consectetur adipisicing elit. Quisquam magnam qui repudiandae nisi nulla ab labore dolore sit voluptatum. Doloremque officiis commodi ab nulla, saepe impedit esse eum assumenda nobis!

Lorem ipsum dolor, sit amet consectetur adipisicing elit. Quae excepturi at iste dolorum dignissimos id labore qui, adipisci expedita voluptas esse magni aut quis asperiores ullam numquam ipsa quo incidunt?

Example Drawing Circles

Example Drawing Polygons

Download Code

To Do

  1. Maybe add something that rounds the geojson coordinates for the polygon input to only 6 or 7 decimal places. As seen on the Wikipedia page for decimal degrees, a latitude and longitude pair rounded off to 7 decimal places would have a maximum uncertainty of 11.1 millimeters.
  2. There is a bug where sometimes the drawing mode cannot be changed back to a state where you can draw on the map. This needs to be fixed. This happens when the page with a map is navigated to, the navigated away from, and then navigated back to. In this google maps implementation, I have tried:
    • Deleting the map instances before navigating away from the page while also removing all the innerHTML of the <div>s that contain maps and removing the <style> and <script> tags that the Google Maps JavaScript API injects in the page,
    • preserving the <style> and <script> tags that the Google Maps JavaScript API injects in the page while deleting the map objects in the custom client-side JavaScript so they are reloaded when the maps are encountered again. This is the current behavior.
  3. Add an event listener on each <section> that contains a map to listen for a custom Event called 'RemoveAllGeography' - or something - that would remove all geographies (markers, polygons, and circles) from the map.