For a lot of kids, Halloween is a magical time, a favorite holiday where you can roam the streets, knocking on doors, demanding candy and treats, all in a cool costume. When I was a kid in Missouri (a state in the center of the USA), Halloween was a time when I got to hang out with my three older brothers and have a great time.
Witch or Bee this year? Let me know in the comments!
At some point, you're supposed to be too old for Halloween (NEVER), but when I was a student in Paris in the '90s we expats still dressed up and rode the Métro in wild costumes. Here in the town where I now live on the East coast of the US, it's cold in late October but still, kids dress up (maybe with a coat over their outfit) and come to collect their M&Ms. Halloween is awesome.
EVERYONE loves Halloween!
But in 2020, Halloween is in jeopardy. How do you safely trick or treat in a pandemic? Each town seems to be making their own policy; in Wellesley (Massachusetts, near Boston, where I live), the official stance is that people can make their own choice to open their door to kids or not. This is a decision that needs some solid parental organization behind it to ensure that the experience is still fun.
Enter...Azure Maps!
I never saw a situation that need a custom mapping solution as badly as this one. So, in short order, I created a concept: Candy Caches, mapped on an Azure Static Web App using an Azure Map.
Building the Site
Making the web site was a snap. It took only a few steps to launch it as an Azure Static Web App:
Get your map key
Get an Azure Map Key by creating an Azure Map:
Scaffold your site
Use the Vue CLI to scaffold a basic Vue site with one page:
vue create halloween-maps
. This app is built with Vue 2, but could easily be converted to Vue 3.
Commit this code to GitHub and then connect that repo to Azure Static Web Apps using the Azure Visual Studio Code Extension. A GitHub Action workflow file will be scaffolded for you provided with the name of a site where your app will live. Each time you commit to your repo, a fresh build will be kicked off.
Add a function
Add a function via the Azure Visual Studio Code Extension for Azure Static Web Apps. This function lives in its own folder called api
and contains minimal code, just fetching the VUE_APP_MAP_KEY environment variable that is stored in Azure Static Web Apps:
module.exports = function (context) {
let key = process.env['VUE_APP_MAP_KEY'];
context.res = { body: key };
context.done();
};
Since we want to store our map key in the Azure Static Web Apps portal and not expose it on GitHub, we need a function to call it. Fortunately you can create all these things in the Azure Visual Studio Code Extension.
Store your map key in your Static Web App portal. For local development, use a local.settings.json file that's not committed to GitHub.
For local development of your site and its api, make sure you have a
vue.config.js
file available with the following server proxy so your API can run locally on port 7071.
devServer: {
proxy: {
'/api': {
target: 'http://localhost:7071',
ws: true,
changeOrigin: true,
},
},
},
Build your map
Install the "azure-maps-control"
package via npm and make sure to import the package into your app at the top of the <script>
block:
import * as atlas from "azure-maps-control";
Then, implement your map:
First, set up a <div>
in your <template>
:
<div id="myMap"></div>
Then, set up some initial data while the map draws to screen:
data: () => ({
map: null,
zoom: 13,//tweak this value to zoom the map in and out
center: [-71.2757724, 42.3123219],//map centers here
subKey: null,//subscription key
}),
Create a mounted
lifecycle hook to get your API key from your function and then pass it to the function that draws your map:
async mounted() {
try {
//get the key
const response = await axios.get("/api/getKey");
this.subKey = response.data;
//draw the map
this.initMap(this.subKey);
} catch (error) {
console.error(error);
}
}
The initMap
function in the methods
block starts the map building routine:
async initMap(key) {
this.map = new atlas.Map("myMap", {
center: this.center,
zoom: this.zoom,
view: "Auto",
authOptions: {
authType: "subscriptionKey",
subscriptionKey: key,
},
});
await this.buildMap();
}
Finally, in this large function, the map is constructed and injected into the myMap
div:
buildMap() {
let self = this;
self.map.events.add("ready", function () {
//Create a data source and add it to the map.
let mapSource = new atlas.source.DataSource();
self.map.sources.add(mapSource);
mapSource.add(data);
let popupSource = new atlas.source.DataSource();
self.map.sources.add(popupSource);
popupSource.add(data);
//add a popup
var symbolLayer = new atlas.layer.SymbolLayer(popupSource);
//Add the polygon and line the symbol layer to the map.
self.map.layers.add(symbolLayer);
var popupTemplate =
'<div style="padding:10px;color:white;font-size:11pt;font-weight:bold">{clue}<br/>{sitename}<br/>{refShort}<br/>{time}</div>';
//Create a popup but leave it closed so we can update it and display it later.
let popup = new atlas.Popup({
pixelOffset: [0, -18],
closeButton: true,
fillColor: "rgba(0,0,0,0.8)",
});
//Add a hover event to the symbol layer.
self.map.events.add("mouseover", symbolLayer, function (e) {
//Make sure that the point exists.
if (e.shapes && e.shapes.length > 0) {
var content, coordinate;
var properties = e.shapes[0].getProperties();
content = popupTemplate
.replace(/{clue}/g, properties.clue)
.replace(/{sitename}/g, properties.sitename)
.replace(/{refShort}/g, properties.refShort)
.replace(/{time}/g, properties.time);
coordinate = e.shapes[0].getCoordinates();
popup.setOptions({
content: content,
position: coordinate,
});
popup.open(self.map);
}
});
self.map.events.add("mouseleave", symbolLayer, function () {
popup.close();
});
});
}
Notice the "symbol layer" that is constructed; these are the little popup flags that contain data about your candy caches.
The map is fed by a file in a format called 'GeoJSON'. This was a new format for me, but it works seamlessly once you understand how the data is laid out. Each point on the map is fed like thus:
//anonymized example
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [-71.4567, 42.1234]
},
"properties": {
"clue": "Look for the scary archway!",
"sitename": "Smith Residence",
"refShort": "123 Weston Road",
"time": "4-6pm"
}
}
You can determine coordinates of residences by saving a Postman call and feeding addresses into it. Use your subscription key to get the address's data:
WOO (Winning Others Over)
Now comes the hard part: convincing townspeople to participate in creating these contact-less candy caches and registering for the web site. I reached out to our local paper (the Swellesley Report editor, Bob Brown, is a pal) and to our town's Facebook group, "What's Up, Wellesley" and garnered a lot of interest! By creating a form, I have set up a process whereby townspeople can tell me their schedule, location, and clue and I can register their cache. We have over ten caches listed, and more on the way.
Want to create your own candy cache? The full repo is here: Halloween Maps. Don't be scared! Follow along as our site grows by visiting it. Tell me more about how you are celebrating your holidays this weird, weird year by adding a note in the comments.