I wanted to take a break from writing about Python scripts and Leaflet.js maps to share a project from a few years ago. But don’t worry, I’ve shared a few JavaScript snippets at the end. As a county government GIS agency, we support various departments in our organization. Our Vector Control department was interested in using mapping technology in their placement of mosquito sampling traps, a part of their West Nile Virus Control Program. They set traps throughout the county, and then send the samples to our state’s environmental agency to be tested for the virus.
In addition to sampling mosquitoes, they conduct spraying throughout the county to control the mosquito population. Vector Control had an interest in using web maps to provide the public awareness about the locations of currently placed traps, as well as the routes for scheduled sprayings. As our department had been utilizing Esri’s ArcGIS Online (AGOL) environment more, and with the release of the Collector App, we thought these products would be great solutions for this project.
We created an AGOL group for Vector Control, which allowed us to provide access to maps and some map services to only this department. They could edit the locations of traps in the field on their phones, or at the office through a web browser. When it was time to update the map of spray routes, they would simply overwrite an existing service.
We developed separate embedded maps of current collection sites and mosquito spray routes to be placed on the County’s website. These were originally iframes from an ArcGIS Online web map, but later replaced with Leaflet.js script-based maps (guess I had to mention Leaflet!). This was a great improvement, as it provided the public with a real-time picture of sampling locations and scheduled spray routes! The spray routes map can be found at this website. At the time of this writing, there are no scheduled spraying events.
As a newcomer to map/feature services at the time this project took place, the biggest challenge for me was figuring out how to have the same feature class in our enterprise geodatabase be available as a secured service for editing within the Collector App, but also available as an unsecured, read-only map service for the public facing web map. Today, the solution seems simple and straightforward:
- Secured service pointing to feature class used for editing
- Unsecured service pointing to feature class for web map display
I created a graphic to explain the process to Vector Control, and I’ll share it below. Unfortunately, for reasons unknown to me, they pulled the public facing map of current collection sites (right after I upgraded it to the new version of Leaflet and associated plugins).
Scripting Bonus
The embedded web maps used two self-invoking functions. The first built the DOM elements for the toggable legend, while the second contained the interactive map logic.
Transitioning from an AGOL web map iframe to a Leaflet map required me to write some helper functions to properly format output from the map service fields to the pop-up. One of these converted a JSON date to string format. I did some online searching to find the logic for this. The other runs a switch statement on a field using coded domain values to output the expected value (person who set trap). The helper functions and popup constructor code are below:
Date Converter
// Convert JSON date format to plain language format function convertJSONDateToString(jsonDate) { var shortDate = null; if (jsonDate) { var regex = /-?\d+/; var matches = regex.exec(jsonDate); var dt = new Date(parseInt(matches[0])); var month = dt.getMonth() + 1; var monthString = month > 9 ? month : '0' + month; var day = dt.getDate(); var dayString = day > 9 ? day : '0' + day; var year = dt.getFullYear(); shortDate = monthString + '-' + dayString + '-' + year; } return shortDate; }
Coded Domains Switch Statement
// Convert domain values of collector to name function convertDomainIDToName(vectorDomainCollector) { var vectorNameCollector = null; switch (vectorDomainCollector) { case '1': vectorNameCollector = 'J. Bitner'; break; case '2': vectorNameCollector = 'J. Gangai'; break; case '3': vectorNameCollector = 'S. Summers'; break; case '4': vectorNameCollector = 'Intern'; break; default: vectorNameCollector = 'Other'; } return vectorNameCollector; }
Popup Constructor
// Add popup to collection sites currentCollectionSites.bindPopup(function(evt) { // Convert JSON date format to MM-DD-YYY format var jsonDate = evt.feature.properties.COLLECTED; var collectedDateContent = convertJSONDateToString(jsonDate); // Convert Collector domain value to name var vectorDomainCollector = evt.feature.properties.COLLECTOR; var vectorNameContent = convertDomainIDToName(vectorDomainCollector); // content for popup return L.Util.template(' <div class="feat-popup"> <h2>Collection Site: {SITE}</h2> <ul> <li>Date Collected: ' + collectedDateContent + '</li> <li>Site Address: {ADDRESS}</li> <li>Municipality: {MUNI}</li> <li>Collected By: ' + vectorNameContent + '</li> <li>Trap Type: {TRAP}</li> <li>Notes: {NOTE}</li> </ul> </div> ', evt.feature.properties); }, {closeOnClick: true, maxHeight: 250, maxWidth: setPopupMaxWidth(windowWidth)});