Sean Gillies (Posts about javascript)https://sgillies.net/tags/javascript.atom2023-12-31T01:26:22ZSean GilliesNikolaJavaScript mapping in 2018https://sgillies.net/2019/05/22/javascript-mapping-in-2018.html2019-05-22T08:00:52-06:002019-05-22T08:00:52-06:00Sean Gillies<p>It's been five-and-a-half years since I <a class="reference external" href="https://sgillies.net/2013/10/08/linking-geojson.html">last blogged</a> about making maps in
a browser using JavaScript. It's cool that my old Syriac Places demo <a class="reference external" href="http://sgillies.github.io/syriaca/">still
works</a>. Yay for web standards. A lot of
things have changed in the meanwhile and this is my first chance to acknowledge
some of those changes.</p>
<p>I've been writing HTML and JavaScript every other day at work for the past
3 weeks, building a web app to give Sales and sales-supporting teams at Mapbox
insight into our imagery basemap. It's HTML and mostly Vanilla JS and has been
a fun exercise. Since it's for internal customers only I can use new browser
and JavaScript features.</p>
<p>Mapbox GL JS, the JavaScript mapping library that's maintained by my coworkers,
is the closest thing to a framework in my app. My current project is the first
in which I've used Mapbox GL JS. I'd like to point out some features and
aspects of the framework that I find especially notable.</p>
<p>Mapbox GL JS has excellent examples. This is a result of I cribbed freely from
<a class="reference external" href="https://docs.mapbox.com/mapbox-gl-js/examples/">https://docs.mapbox.com/mapbox-gl-js/examples/</a> and everything worked. Not all
projects have good examples. John Firebaugh (who has since moved on from
Mapbox), Colleen McGinnis, and the GL JS and Documentation teams put a ton of
work into these and it shows.</p>
<p>Mapbox GL JS makes a big and important case of dynamic maps simple. I have
a GeoJSON Source defined in my app's JavaScript. It is registered with a layer
and a map. When I remove features from the source, they disappear from the
map. When I add features to the source, they appear on the map. The map is
updated fast enough that I can do this on every mousemove event.</p>
<p>Mapbox GL JS Expressions give a developer ample control over styling and
filtering while keeping the styling API abstract and simple. In several parts
of its API, Mapbox GL JS can evaluate Lisp-like expressions composed of
JavaScript arrays. <code class="docutils literal"><span class="pre">["+",</span> 4, <span class="pre">["*",</span> 3, 2]]</code> evaluates to <code class="docutils literal">4 + (3 * 2)</code>, or
<code class="docutils literal">10</code>. In addition to the standard JavaScript operators, the namespace of GL
JS expressions operators contains feature property and map attribute getters.
An advantages of using JavaScript objects instead of conventional Lisp (as with
<a class="reference external" href="http://localhost:8000/2019/05/16/rasterio-1-0-23.html">snuggs and rio-calc</a>) is that developers
get syntax highlighting help when writing expressions, and can use JavaScript
to build expressions.</p>
<p>Another first for me is that I'm using Turf, the well-known simple features
geometry library created by Morgan Herlocker. In my app I'm creating features
on the fly derived from the intersections of features in a Mapbox tileset. Turf
is fast and its functions take and return GeoJSON objects, which makes it easy
for me to use and easy to integrate with Mapbox GL JS and other libraries or
plugins. Turf is indispensable for a web app like the one I'm writing.</p>
<p>Lastly, JavaScript itself has changed a lot in the last 5 years. I'm using
features of ES6 for the first time ever. You know this, I'll bet, but ES6 arrow
functions are like Scheme's lambda expressions and Python's lambda forms. I'm
using them exclusively, instead of the old anonymous functions, when filtering
and sorting arrays of features. For example, the following sorts features by
a date, in descending date order, without needing to type "function" or
"return". Handy, and to my eyes, more readable.</p>
<div class="code"><pre class="code javascript"><a id="rest_code_c1aff86d1159403b92e353d42b01dd50-1" name="rest_code_c1aff86d1159403b92e353d42b01dd50-1" href="https://sgillies.net/2019/05/22/javascript-mapping-in-2018.html#rest_code_c1aff86d1159403b92e353d42b01dd50-1"></a><span class="nx">features</span><span class="p">.</span><span class="nx">sort</span><span class="p">((</span><span class="nx">a</span><span class="p">,</span><span class="w"> </span><span class="nx">b</span><span class="p">)</span><span class="w"> </span><span class="p">=></span><span class="w"> </span><span class="nx">b</span><span class="p">.</span><span class="nx">properties</span><span class="p">.</span><span class="nx">render_date</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="nx">a</span><span class="p">.</span><span class="nx">properties</span><span class="p">.</span><span class="nx">render_date</span><span class="p">)</span><span class="w"></span>
</pre></div>
<p>These are obviously not the only recent advances in the JavaScript mapping
field. Or even the most recent, which is why I chose the title "JavaScript
mapping in 2018". The app that I'm building could have been written a year ago
using the same software. For a glimpse at JavaScript mapping in 2019-2020,
I suggest <a class="reference external" href="https://observablehq.com/collection/@observablehq/maps">https://observablehq.com/collection/@observablehq/maps</a>.</p>Linking GeoJSONhttps://sgillies.net/2013/10/08/linking-geojson.html2013-10-08T00:00:00-06:002013-10-08T00:00:00-06:00Sean Gillies<p>I've blogged a few times (
<a class="reference external" href="http://sgillies.net/blog/888/openlayers-constrained-by-hypertext/">here</a>
and <a class="reference external" href="http://sgillies.net/blog/958/geojson-data-uris/">here</a>) about a pattern
I've developed for mapping feature data associated with a web page.
Lyzi Diamond wrote a great post about this pattern a few weeks ago:
<a class="reference external" href="http://lyzidiamond.com/posts/osgeo-august-meeting/">http://lyzidiamond.com/posts/osgeo-august-meeting/</a>. I'm thrilled that it's
catching on a bit. The pattern is super simple, only three short paragraphs are
needed to describe it.</p>
<p>Let's say I have a collection of web pages. Each is about a particular batch of
features. <a class="reference external" href="http://sgillies.github.io/syriaca/">http://sgillies.github.io/syriaca/</a>, for example, is a demo of a page
about Syriac places in antiquity. It bears a map in which the places are
rendered.</p>
<p>The features to be rendered in the map are obtained by making an HTTP request
for a GeoJSON resource. In my case
<a class="reference external" href="http://sgillies.github.io/syriaca/syriaca.json">http://sgillies.github.io/syriaca/syriaca.json</a> is fetched using
jQuery.getJSON().</p>
<div class="code"><pre class="code javascript"><a id="rest_code_600b65a00e3b4d6ea3aaa0f44c23e564-1" name="rest_code_600b65a00e3b4d6ea3aaa0f44c23e564-1" href="https://sgillies.net/2013/10/08/linking-geojson.html#rest_code_600b65a00e3b4d6ea3aaa0f44c23e564-1"></a><span class="kd">var</span><span class="w"> </span><span class="nx">map</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">L</span><span class="p">.</span><span class="nx">map</span><span class="p">(</span><span class="s1">'map'</span><span class="p">);</span><span class="w"></span>
<a id="rest_code_600b65a00e3b4d6ea3aaa0f44c23e564-2" name="rest_code_600b65a00e3b4d6ea3aaa0f44c23e564-2" href="https://sgillies.net/2013/10/08/linking-geojson.html#rest_code_600b65a00e3b4d6ea3aaa0f44c23e564-2"></a><span class="kd">var</span><span class="w"> </span><span class="nx">geojson</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">L</span><span class="p">.</span><span class="nx">geoJson</span><span class="p">().</span><span class="nx">addTo</span><span class="p">(</span><span class="nx">map</span><span class="p">);</span><span class="w"></span>
<a id="rest_code_600b65a00e3b4d6ea3aaa0f44c23e564-3" name="rest_code_600b65a00e3b4d6ea3aaa0f44c23e564-3" href="https://sgillies.net/2013/10/08/linking-geojson.html#rest_code_600b65a00e3b4d6ea3aaa0f44c23e564-3"></a>
<a id="rest_code_600b65a00e3b4d6ea3aaa0f44c23e564-4" name="rest_code_600b65a00e3b4d6ea3aaa0f44c23e564-4" href="https://sgillies.net/2013/10/08/linking-geojson.html#rest_code_600b65a00e3b4d6ea3aaa0f44c23e564-4"></a><span class="nx">$</span><span class="p">.</span><span class="nx">getJSON</span><span class="p">(</span><span class="nx">geojson_uri</span><span class="p">,</span><span class="w"> </span><span class="kd">function</span><span class="w"> </span><span class="p">(</span><span class="nx">data</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<a id="rest_code_600b65a00e3b4d6ea3aaa0f44c23e564-5" name="rest_code_600b65a00e3b4d6ea3aaa0f44c23e564-5" href="https://sgillies.net/2013/10/08/linking-geojson.html#rest_code_600b65a00e3b4d6ea3aaa0f44c23e564-5"></a><span class="w"> </span><span class="nx">geojson</span><span class="p">.</span><span class="nx">addData</span><span class="p">(</span><span class="nx">data</span><span class="p">);</span><span class="w"></span>
<a id="rest_code_600b65a00e3b4d6ea3aaa0f44c23e564-6" name="rest_code_600b65a00e3b4d6ea3aaa0f44c23e564-6" href="https://sgillies.net/2013/10/08/linking-geojson.html#rest_code_600b65a00e3b4d6ea3aaa0f44c23e564-6"></a><span class="p">});</span><span class="w"></span>
</pre></div>
<p>The feature data is not written into a script in the page as
they are in <a class="reference external" href="http://leafletjs.com/examples/quick-start-example.html">http://leafletjs.com/examples/quick-start-example.html</a>. Yes,
this means an extra HTTP request, but one that can be ansynchronous and
cacheable.</p>
<p>Here's the crux of the pattern: the URI of that GeoJSON resource bound to the
geojson_uri variable is specified not in the map script, but in a link in the
head of the page.</p>
<div class="code"><pre class="code html"><a id="rest_code_74570743e1b34efe99deca20b9ea062b-1" name="rest_code_74570743e1b34efe99deca20b9ea062b-1" href="https://sgillies.net/2013/10/08/linking-geojson.html#rest_code_74570743e1b34efe99deca20b9ea062b-1"></a><span class="p"><</span><span class="nt">link</span> <span class="na">rel</span><span class="o">=</span><span class="s">"location"</span> <span class="na">type</span><span class="o">=</span><span class="s">"application/json"</span> <span class="na">href</span><span class="o">=</span><span class="s">"syriaca.json"</span><span class="p">/></span>
</pre></div>
<p>That URI is found by its "location" relation type.</p>
<div class="code"><pre class="code javascript"><a id="rest_code_84a4e6986b0c4611a8f4a527aeebc8e6-1" name="rest_code_84a4e6986b0c4611a8f4a527aeebc8e6-1" href="https://sgillies.net/2013/10/08/linking-geojson.html#rest_code_84a4e6986b0c4611a8f4a527aeebc8e6-1"></a><span class="kd">var</span><span class="w"> </span><span class="nx">geojson_uri</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">$</span><span class="p">(</span><span class="s1">'link[rel="location"]'</span><span class="p">).</span><span class="nx">attr</span><span class="p">(</span><span class="s2">"href"</span><span class="p">);</span><span class="w"></span>
</pre></div>
<p>What's gained by having my Javascript follow a link to get GeoJSON features?
Why not just write the features into a script? I've developed this pattern
because it decouples the map from the data and lets me generalize the mapping
script so I can reuse it across many pages, and also because I'm not thinking
just about the slippy maps in the page. I'm keeping the bigger web in mind.</p>
<p>If the Javascript in my page was my only consideration I'd just embed the
features in a script and maybe not use GeoJSON at all. But a resource like
<a class="reference external" href="http://sgillies.github.io/syriaca/">http://sgillies.github.io/syriaca/</a> isn't just a page with a slippy map, it's
a starting point for other web map making software. The links are also for web
clients written by people other than me.</p>
<p>Given the page's URL, a Python web client can get the same GeoJSON in the
same way.</p>
<div class="code"><pre class="code python"><a id="rest_code_fa0c4b50849a43399a7cf1f17cd2be1b-1" name="rest_code_fa0c4b50849a43399a7cf1f17cd2be1b-1" href="https://sgillies.net/2013/10/08/linking-geojson.html#rest_code_fa0c4b50849a43399a7cf1f17cd2be1b-1"></a><span class="kn">from</span> <span class="nn">bs4</span> <span class="kn">import</span> <span class="n">BeautifulSoup</span>
<a id="rest_code_fa0c4b50849a43399a7cf1f17cd2be1b-2" name="rest_code_fa0c4b50849a43399a7cf1f17cd2be1b-2" href="https://sgillies.net/2013/10/08/linking-geojson.html#rest_code_fa0c4b50849a43399a7cf1f17cd2be1b-2"></a><span class="kn">import</span> <span class="nn">requests</span>
<a id="rest_code_fa0c4b50849a43399a7cf1f17cd2be1b-3" name="rest_code_fa0c4b50849a43399a7cf1f17cd2be1b-3" href="https://sgillies.net/2013/10/08/linking-geojson.html#rest_code_fa0c4b50849a43399a7cf1f17cd2be1b-3"></a>
<a id="rest_code_fa0c4b50849a43399a7cf1f17cd2be1b-4" name="rest_code_fa0c4b50849a43399a7cf1f17cd2be1b-4" href="https://sgillies.net/2013/10/08/linking-geojson.html#rest_code_fa0c4b50849a43399a7cf1f17cd2be1b-4"></a><span class="n">r</span> <span class="o">=</span> <span class="n">requests</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s1">'http://sgillies.github.io/syriaca/'</span><span class="p">)</span>
<a id="rest_code_fa0c4b50849a43399a7cf1f17cd2be1b-5" name="rest_code_fa0c4b50849a43399a7cf1f17cd2be1b-5" href="https://sgillies.net/2013/10/08/linking-geojson.html#rest_code_fa0c4b50849a43399a7cf1f17cd2be1b-5"></a><span class="n">soup</span> <span class="o">=</span> <span class="n">BeautifulSoup</span><span class="p">(</span><span class="n">r</span><span class="o">.</span><span class="n">content</span><span class="p">)</span>
<a id="rest_code_fa0c4b50849a43399a7cf1f17cd2be1b-6" name="rest_code_fa0c4b50849a43399a7cf1f17cd2be1b-6" href="https://sgillies.net/2013/10/08/linking-geojson.html#rest_code_fa0c4b50849a43399a7cf1f17cd2be1b-6"></a><span class="n">points</span> <span class="o">=</span> <span class="p">[</span><span class="n">t</span> <span class="k">for</span> <span class="n">t</span> <span class="ow">in</span> <span class="n">soup</span><span class="o">.</span><span class="n">find_all</span><span class="p">(</span><span class="s1">'link'</span><span class="p">)</span> <span class="k">if</span> <span class="s1">'location'</span> <span class="ow">in</span> <span class="n">t</span><span class="p">[</span><span class="s1">'rel'</span><span class="p">]]</span>
<a id="rest_code_fa0c4b50849a43399a7cf1f17cd2be1b-7" name="rest_code_fa0c4b50849a43399a7cf1f17cd2be1b-7" href="https://sgillies.net/2013/10/08/linking-geojson.html#rest_code_fa0c4b50849a43399a7cf1f17cd2be1b-7"></a><span class="nb">print</span> <span class="n">points</span>
<a id="rest_code_fa0c4b50849a43399a7cf1f17cd2be1b-8" name="rest_code_fa0c4b50849a43399a7cf1f17cd2be1b-8" href="https://sgillies.net/2013/10/08/linking-geojson.html#rest_code_fa0c4b50849a43399a7cf1f17cd2be1b-8"></a>
<a id="rest_code_fa0c4b50849a43399a7cf1f17cd2be1b-9" name="rest_code_fa0c4b50849a43399a7cf1f17cd2be1b-9" href="https://sgillies.net/2013/10/08/linking-geojson.html#rest_code_fa0c4b50849a43399a7cf1f17cd2be1b-9"></a><span class="c1"># Output:</span>
<a id="rest_code_fa0c4b50849a43399a7cf1f17cd2be1b-10" name="rest_code_fa0c4b50849a43399a7cf1f17cd2be1b-10" href="https://sgillies.net/2013/10/08/linking-geojson.html#rest_code_fa0c4b50849a43399a7cf1f17cd2be1b-10"></a><span class="c1"># [<link href="http://sgillies.github.io/syriaca/syriaca.json" rel="location"/>]</span>
<a id="rest_code_fa0c4b50849a43399a7cf1f17cd2be1b-11" name="rest_code_fa0c4b50849a43399a7cf1f17cd2be1b-11" href="https://sgillies.net/2013/10/08/linking-geojson.html#rest_code_fa0c4b50849a43399a7cf1f17cd2be1b-11"></a>
<a id="rest_code_fa0c4b50849a43399a7cf1f17cd2be1b-12" name="rest_code_fa0c4b50849a43399a7cf1f17cd2be1b-12" href="https://sgillies.net/2013/10/08/linking-geojson.html#rest_code_fa0c4b50849a43399a7cf1f17cd2be1b-12"></a><span class="k">for</span> <span class="n">p</span> <span class="ow">in</span> <span class="n">points</span><span class="p">:</span>
<a id="rest_code_fa0c4b50849a43399a7cf1f17cd2be1b-13" name="rest_code_fa0c4b50849a43399a7cf1f17cd2be1b-13" href="https://sgillies.net/2013/10/08/linking-geojson.html#rest_code_fa0c4b50849a43399a7cf1f17cd2be1b-13"></a> <span class="n">s</span> <span class="o">=</span> <span class="n">requests</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">r</span><span class="o">.</span><span class="n">url</span> <span class="o">+</span> <span class="n">p</span><span class="p">[</span><span class="s1">'href'</span><span class="p">])</span>
<a id="rest_code_fa0c4b50849a43399a7cf1f17cd2be1b-14" name="rest_code_fa0c4b50849a43399a7cf1f17cd2be1b-14" href="https://sgillies.net/2013/10/08/linking-geojson.html#rest_code_fa0c4b50849a43399a7cf1f17cd2be1b-14"></a> <span class="n">json</span> <span class="o">=</span> <span class="n">s</span><span class="o">.</span><span class="n">json</span><span class="p">()</span>
<a id="rest_code_fa0c4b50849a43399a7cf1f17cd2be1b-15" name="rest_code_fa0c4b50849a43399a7cf1f17cd2be1b-15" href="https://sgillies.net/2013/10/08/linking-geojson.html#rest_code_fa0c4b50849a43399a7cf1f17cd2be1b-15"></a> <span class="nb">print</span> <span class="n">json</span><span class="p">[</span><span class="s1">'type'</span><span class="p">],</span> <span class="nb">len</span><span class="p">(</span><span class="n">json</span><span class="p">[</span><span class="s1">'features'</span><span class="p">])</span>
<a id="rest_code_fa0c4b50849a43399a7cf1f17cd2be1b-16" name="rest_code_fa0c4b50849a43399a7cf1f17cd2be1b-16" href="https://sgillies.net/2013/10/08/linking-geojson.html#rest_code_fa0c4b50849a43399a7cf1f17cd2be1b-16"></a>
<a id="rest_code_fa0c4b50849a43399a7cf1f17cd2be1b-17" name="rest_code_fa0c4b50849a43399a7cf1f17cd2be1b-17" href="https://sgillies.net/2013/10/08/linking-geojson.html#rest_code_fa0c4b50849a43399a7cf1f17cd2be1b-17"></a><span class="c1"># Output:</span>
<a id="rest_code_fa0c4b50849a43399a7cf1f17cd2be1b-18" name="rest_code_fa0c4b50849a43399a7cf1f17cd2be1b-18" href="https://sgillies.net/2013/10/08/linking-geojson.html#rest_code_fa0c4b50849a43399a7cf1f17cd2be1b-18"></a><span class="c1"># FeatureCollection 955</span>
</pre></div>
<p>The GeoJSON URI is much easier to find in a link by its relation type than
by scraping it from a script (were I to use a literal in my first code
example above instead of a geojson_uri variable). There's a IETF draft
written by James Snell proposing standardization of the "location" relation:
<a class="reference external" href="http://tools.ietf.org/html/draft-snell-more-link-relations-01">http://tools.ietf.org/html/draft-snell-more-link-relations-01</a>. My pattern
becomes much more useful with such a standard link relation.</p>
<p>Does this pattern avoid the browser's same-origin policy as Lyzi suggests in
<a class="reference external" href="http://lyzidiamond.com/posts/external-geojson-and-leaflet-the-other-way/">http://lyzidiamond.com/posts/external-geojson-and-leaflet-the-other-way/</a>? As
far as I know, links with rel="stylesheet" are the only ones that a browser
fetches unprompted. The GeoJSON fetched by XHR is still subject to same-origin
policy.</p>