https://www.endpointdev.com/blog/tags/leaflet/2021-03-24T00:00:00+00:00End Point DevSpatial queries with MySQLhttps://www.endpointdev.com/blog/2021/03/spatial-queries-with-mysql/2021-03-24T00:00:00+00:00Juan Pablo Ventoso
<p><img src="/blog/2021/03/spatial-queries-with-mysql/spatial-queries-with-mysql.jpg" alt="Spatial queries with MySQL">
<a href="https://flic.kr/p/nQNYxQ">Photo</a> by <a href="https://www.flickr.com/photos/119810478@N08/">Francois Powell</a>, <a href="https://creativecommons.org/licenses/by/2.0/">CC BY 2.0</a>, cropped</p>
<p>MySQL is one of the most widely used relational databases. Most PHP websites rely on MySQL for persisting their information, which makes it one of the <a href="https://towardsdatascience.com/top-10-databases-to-use-in-2021-d7e6a85402ba">DB-Engines top four most popular databases</a> along with Oracle, SQL Server, and PostgreSQL.</p>
<p>One of its capabilities that is not very well known is that the engine supports working with spatial data, allowing you to save different shapes (points, lines, polygons) and querying information based on intersections, distances, or overlaps. This capability was included in MySQL a long time ago, but it became easier to use starting in version <a href="https://dev.mysql.com/doc/refman/5.6/en/">5.6</a>, when the distance and point intersection functions were added.</p>
<p>Spatial data can be useful for many needs, including:</p>
<ul>
<li>Searching for places based on latitude/longitude coordinates</li>
<li>Displaying information and areas as layers on maps</li>
<li>Architecture or home design applications</li>
</ul>
<p>My first experience with spatial queries was for a weather website I developed that displays local alerts/warnings on a map using MySQL spatial functions to return active weather alerts for a given location, or to inform if lightning has been observed near the user’s current coordinates. So far, MySQL has given me all the resources I need to do such operations with relatively good performance and without needing to write lots of custom code.</p>
<h3 id="adding-spatial-information">Adding spatial information</h3>
<p>There are many resources available to import spatial information into our database. From the United States Census Bureau we can find a <a href="https://www.census.gov/geographies/mapping-files/time-series/geo/carto-boundary-file.html">set of shapefiles with all US states and counties</a>. The <a href="https://www.back4app.com/database">Back4App social database platform</a> also has many datasets available to download for free.</p>
<p>Of course, we can also create a table ourselves that contains any kind of spatial information. In the example below, we will create a table named restaurants which will have a name and location (lat/long) geometry for each row.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-sql" data-lang="sql"><span style="color:#080;font-weight:bold">CREATE</span><span style="color:#bbb"> </span><span style="color:#080;font-weight:bold">TABLE</span><span style="color:#bbb"> </span>restaurants<span style="color:#bbb"> </span>(<span style="color:#bbb">
</span><span style="color:#bbb"> </span>name<span style="color:#bbb"> </span><span style="color:#038">VARCHAR</span>(<span style="color:#00d;font-weight:bold">100</span>),<span style="color:#bbb">
</span><span style="color:#bbb"> </span><span style="color:#080;font-weight:bold">location</span><span style="color:#bbb"> </span>GEOMETRY<span style="color:#bbb"> </span><span style="color:#080;font-weight:bold">NOT</span><span style="color:#bbb"> </span><span style="color:#080;font-weight:bold">NULL</span>,<span style="color:#bbb">
</span><span style="color:#bbb"> </span>SPATIAL<span style="color:#bbb"> </span><span style="color:#080;font-weight:bold">INDEX</span>(<span style="color:#080;font-weight:bold">location</span>)<span style="color:#bbb">
</span><span style="color:#bbb"></span>);<span style="color:#bbb">
</span></code></pre></div><p>Note that we are adding an index of type SPATIAL to the location field. This is mainly for performance reasons. Once the table is created, we can populate it with some data using the <code>ST_GeomFromText()</code> function that will convert a string representation of any shape into a geometry:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-sql" data-lang="sql"><span style="color:#080;font-weight:bold">INSERT</span><span style="color:#bbb"> </span><span style="color:#080;font-weight:bold">INTO</span><span style="color:#bbb"> </span>restaurants<span style="color:#bbb"> </span><span style="color:#080;font-weight:bold">VALUES</span><span style="color:#bbb"> </span>(<span style="color:#d20;background-color:#fff0f0">'Restaurant 1'</span>,<span style="color:#bbb"> </span>ST_GeomFromText(<span style="color:#d20;background-color:#fff0f0">'POINT(-26.66115 40.95858)'</span>));<span style="color:#bbb">
</span><span style="color:#bbb"></span><span style="color:#080;font-weight:bold">INSERT</span><span style="color:#bbb"> </span><span style="color:#080;font-weight:bold">INTO</span><span style="color:#bbb"> </span>restaurants<span style="color:#bbb"> </span><span style="color:#080;font-weight:bold">VALUES</span><span style="color:#bbb"> </span>(<span style="color:#d20;background-color:#fff0f0">'Restaurant 2'</span>,<span style="color:#bbb"> </span>ST_GeomFromText(<span style="color:#d20;background-color:#fff0f0">'POINT(-26.68685 40.93992)'</span>));<span style="color:#bbb">
</span><span style="color:#bbb"></span><span style="color:#080;font-weight:bold">INSERT</span><span style="color:#bbb"> </span><span style="color:#080;font-weight:bold">INTO</span><span style="color:#bbb"> </span>restaurants<span style="color:#bbb"> </span><span style="color:#080;font-weight:bold">VALUES</span><span style="color:#bbb"> </span>(<span style="color:#d20;background-color:#fff0f0">'Restaurant 3'</span>,<span style="color:#bbb"> </span>ST_GeomFromText(<span style="color:#d20;background-color:#fff0f0">'POINT(-31.11924 42.39557)'</span>));<span style="color:#bbb">
</span></code></pre></div><h3 id="querying-and-filtering-with-spatial-data">Querying and filtering with spatial data</h3>
<p>Let’s suppose we imported a list of US states from the website above, along with a shape field that holds the geometry associated with each state into a <code>states</code> table. Then, we will be able to get the geometry by just running a SELECT statement:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-sql" data-lang="sql"><span style="color:#080;font-weight:bold">SELECT</span><span style="color:#bbb"> </span>state_name,<span style="color:#bbb"> </span>ST_AsText(shape)<span style="color:#bbb"> </span><span style="color:#080;font-weight:bold">FROM</span><span style="color:#bbb"> </span>states<span style="color:#bbb"> </span><span style="color:#080;font-weight:bold">ORDER</span><span style="color:#bbb"> </span><span style="color:#080;font-weight:bold">BY</span><span style="color:#bbb"> </span>state_name;<span style="color:#bbb">
</span><span style="color:#bbb">
</span><span style="color:#bbb"></span>*<span style="color:#888">-----------------------------*
</span><span style="color:#888"></span>|<span style="color:#bbb"> </span>state_name<span style="color:#bbb"> </span>|<span style="color:#bbb"> </span>shape<span style="color:#bbb"> </span>|<span style="color:#bbb">
</span><span style="color:#bbb"></span>*<span style="color:#888">-----------------------------*
</span><span style="color:#888"></span>|<span style="color:#bbb"> </span>Alabama<span style="color:#bbb"> </span>|<span style="color:#bbb"> </span>POLYGON((...))<span style="color:#bbb"> </span>|<span style="color:#bbb">
</span><span style="color:#bbb"></span>|<span style="color:#bbb"> </span>Alaska<span style="color:#bbb"> </span>|<span style="color:#bbb"> </span>POLYGON((...))<span style="color:#bbb"> </span>|<span style="color:#bbb">
</span><span style="color:#bbb"></span>|<span style="color:#bbb"> </span>Arizona<span style="color:#bbb"> </span>|<span style="color:#bbb"> </span>POLYGON((...))<span style="color:#bbb"> </span>|<span style="color:#bbb">
</span><span style="color:#bbb"></span>|<span style="color:#bbb"> </span>...<span style="color:#bbb"> </span>|<span style="color:#bbb">
</span><span style="color:#bbb"></span>*<span style="color:#888">-----------------------------*
</span></code></pre></div><p>The <code>ST_AsText()</code> function will convert the shape contents into a string representation for us to read and parse, doing the inverse process of the <code>ST_GeomFromText()</code> function we used above. In our application, we can then parse that string and process it the way we need.</p>
<h3 id="intersecting">Intersecting</h3>
<p>For example, we can get a list of states that intersects with a given shape. To do that, we will use the <code>ST_Intersects()</code> function, which will return a boolean value indicating if two shapes intersect or not.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-sql" data-lang="sql"><span style="color:#080;font-weight:bold">SELECT</span><span style="color:#bbb"> </span>s.state_name<span style="color:#bbb"> </span><span style="color:#080;font-weight:bold">FROM</span><span style="color:#bbb"> </span>states<span style="color:#bbb"> </span>s<span style="color:#bbb"> </span><span style="color:#080;font-weight:bold">WHERE</span><span style="color:#bbb"> </span>ST_Intersects(s.shape,<span style="color:#bbb"> </span>ST_GeomFromText(?))<span style="color:#bbb"> </span><span style="color:#080;font-weight:bold">ORDER</span><span style="color:#bbb"> </span><span style="color:#080;font-weight:bold">BY</span><span style="color:#bbb"> </span>s.state_name;<span style="color:#bbb">
</span></code></pre></div><p>We can define our geometry to be any shape we need. For example, if we want to know which state contains a given point, we can set our parameter to something like <code>'POINT(40.69 -74.25)'</code> which should return the state of New York.</p>
<p>If we have a polygon, we can set the parameter to specify a polygon shape, i.e. <code>'POLYGON((40.69 -74.25, 41.10 -74.25, 41.10 -76.11, 40.69 -76.11, 40.69 -74.25))'</code> will mean a rectangle that intersects the states of New York and Pennsylvania. In that case, our query will return two rows with both state names.</p>
<h3 id="spherical-distance">Spherical distance</h3>
<p>Let’s get back to our <code>restaurants</code> table. If we want to get a list of restaurants that are close to our location, we can use the <code>ST_Distance_Sphere()</code> function that will return a distance in meters between two points in a sphere (defaulting to Earth’s radius):</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-sql" data-lang="sql"><span style="color:#080;font-weight:bold">SELECT</span><span style="color:#bbb"> </span>*<span style="color:#bbb"> </span><span style="color:#080;font-weight:bold">FROM</span><span style="color:#bbb"> </span>restaurants<span style="color:#bbb"> </span><span style="color:#080;font-weight:bold">WHERE</span><span style="color:#bbb"> </span>ST_Distance_Sphere(<span style="color:#080;font-weight:bold">location</span>,<span style="color:#bbb"> </span>ST_GeomFromText(?))<span style="color:#bbb"> </span><=<span style="color:#bbb"> </span><span style="color:#00d;font-weight:bold">10</span><span style="color:#bbb"> </span>*<span style="color:#bbb"> </span><span style="color:#00d;font-weight:bold">1000</span><span style="color:#bbb"> </span><span style="color:#080;font-weight:bold">ORDER</span><span style="color:#bbb"> </span><span style="color:#080;font-weight:bold">BY</span><span style="color:#bbb"> </span>name;<span style="color:#bbb">
</span></code></pre></div><p>This query will give us a list of restaurants, sorted by name, that are within 10 kilometers from the point we pass as a parameter. We should set the parameter as <code>'POINT(lat long)'</code> where lat and long represent our current geolocation.</p>
<p><img src="/blog/2021/03/spatial-queries-with-mysql/restaurants-query-example.jpg" alt="Example of a query to the restaurants table"></p>
<p>We can use the distance function for many other purposes, like finding people that are close to each other or planning a road trip for the user.</p>
<h3 id="conclusion">Conclusion</h3>
<p>We can avoid writing our own routines to handle spatial data by using MySQL’s built-in spatial types and methods. For most websites, MySQL offers a set of functions that will provide support for most common scenarios. And starting with version 8.0 MySQL includes extended support for geographic and ellipsoid computations.</p>
<p>We should consider that, if we need a complex solution for a robust spatial-based enterprise application, there are alternatives like <a href="https://postgis.net/">PostgreSQL’s PostGIS</a> that might be better suited to our purpose. MySQL lacks some advanced features like transformations, custom topology handling, or BRIN indexes, that might have an impact on our development process, depending on our needs.</p>
<p>The image below shows several weather alerts rendered on a <a href="https://leafletjs.com/">Leaflet.js</a> map that were fetched from a field of type <code>GEOMETRY</code> in MySQL:</p>
<p><img src="/blog/2021/03/spatial-queries-with-mysql/polygons-in-leafletjs-map.jpg" alt="map of Argentina with areas shaded yellow"></p>
<h3 id="resources">Resources</h3>
<p>There are many other functions we can use to work with our spatial data, from getting the centroid associated to a shape in order to automatically center a map view, to simplifying/optimizing existing geometry. I recommend reviewing the following links to start trying things out:</p>
<ul>
<li><a href="https://dev.mysql.com/doc/refman/8.0/en/spatial-function-reference.html">MySQL spatial function reference (8.0)</a></li>
<li><a href="https://dev.mysql.com/doc/refman/8.0/en/opengis-geometry-model.html">Supported geometry (based on OpenGIS model) (8.0)</a></li>
<li><a href="https://dev.mysql.com/doc/refman/5.7/en/spatial-geojson-functions.html">GeoJSON support functions</a></li>
</ul>
Switching from Google Maps to Leaflethttps://www.endpointdev.com/blog/2019/03/switching-google-maps-leaflet/2019-03-23T00:00:00+00:00Juan Pablo Ventoso
<p><img src="/blog/2019/03/switching-google-maps-leaflet/leaflet-weather-map-us.jpg" alt="Leaflet Weather map example" /><br>Photo: <a href="https://www.extendedforecast.net/radsat">RadSat HD</a></p>
<p>It’s no news for anyone who has Google Maps running on their websites that Google started charging for using their API. We saw it coming when, back in 2016, they started requiring a key to add a map using their JavaScript API. And on June 11, 2018, they did a major upgrade to their API and billing system.</p>
<p><b>The consequence?</b> Any website with more than 25,000 page loads per day will have to pay. And if you are using a dynamic map (a map with custom styling and/or content) you only have roughly 28,000 free monthly page loads. We must create a billing account, <em>even if we have a small website with a couple of daily visitors</em>, hand credit card information to Google, and monitor our stats to make sure we won’t be charged. And if we don’t do that, our map will be dark and will have a “For development only” message in the background.</p>
<p>So what are your options? You can either pay or completely remove Google Maps from your websites. Even enterprise weather websites like <a href="https://weather.com/weather/radar/interactive/l/USNY0996:1:US">The Weather Channel</a> or <a href="https://www.wunderground.com/wundermap">Weather Underground</a> have now replaced their Google Maps API calls with an alternative like Leaflet or MapBox (in some cases, they even gained some functionality in the process).</p>
<p>I have a <a href="https://www.extendedforecast.net">personal weather website</a>, and when I heard big changes were coming, I started to move away from Google Maps as well. My choice at that moment was Leaflet: It has everything you may need to build a robust tile-based map, add layers, markers, animations, custom tiles, etc. And it’s BSD-licensed <b>open source and free</b>.</p>
<h3 id="creating-a-basic-map">Creating a basic map</h3>
<p><img src="/blog/2019/03/switching-google-maps-leaflet/google-vs-leaflet-look-and-feel.jpg" /><br><small>Google Map conversion to Leaflet can be almost seamless if the same tiles are used.</small></p>
<p>Google Maps API and Leaflet share a similar way of doing most things, but they have some key differences we need to take into account. As a general rule, Google used the “google.maps” prefix to name most classes and interfaces, while Leaflet uses the “L” prefix instead.</p>
<p>First thing we need to do is to remove the Google Maps API reference from our website(s). So we need to replace the reference:</p>
<pre tabindex="0"><code><script type="text/javascript" src="https://maps.googleapis.com/maps/api/js?key=[your_api_key]"></script>
</code></pre><p>With the references to the Leaflet map JavaScript and stylesheet URIs.</p>
<pre tabindex="0"><code><script src="https://unpkg.com/leaflet@1.0.2/dist/leaflet.js"></script>
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.0.2/dist/leaflet.css" />
</code></pre><p>Now let’s take a look at the code needed to create a Google Map vs. a Leaflet map.</p>
<ul>
<li>Google:</li>
</ul>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-javascript" data-lang="javascript"><span style="color:#080;font-weight:bold">var</span> map = <span style="color:#080;font-weight:bold">new</span> google.maps.Map(<span style="color:#038">document</span>.getElementById(<span style="color:#d20;background-color:#fff0f0">"map"</span>), {
center: <span style="color:#080;font-weight:bold">new</span> google.maps.LatLng(<span style="color:#00d;font-weight:bold">40.7401</span>, -<span style="color:#00d;font-weight:bold">73.9891</span>),
zoom: <span style="color:#00d;font-weight:bold">12</span>,
mapTypeId: google.maps.MapTypeId.ROADMAP
});
</code></pre></div><p>Leaflet:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-javascript" data-lang="javascript"><span style="color:#080;font-weight:bold">var</span> map = <span style="color:#080;font-weight:bold">new</span> L.Map(<span style="color:#d20;background-color:#fff0f0">"map"</span>, {
center: <span style="color:#080;font-weight:bold">new</span> L.LatLng(<span style="color:#00d;font-weight:bold">40.7401</span>, -<span style="color:#00d;font-weight:bold">73.9891</span>),
zoom: <span style="color:#00d;font-weight:bold">12</span>,
layers: <span style="color:#080;font-weight:bold">new</span> L.TileLayer(<span style="color:#d20;background-color:#fff0f0">"https://tile.openstreetmap.org/{z}/{x}/{y}.png"</span>)
});
</code></pre></div><p>Quite similar, isn’t it? The main difference is that, in Leaflet, we need to provide a tile layer for the base map because there isn’t one by default. There are a lot of excellent tile layers available to use at no cost. Here are some of them:</p>
<ul>
<li><b>Bright</b>: <code>https://a.tiles.mapbox.com/v3/mapbox.world-bright/{z}/{x}/{y}.png</code></li>
<li><b>Topographic</b>: <code>https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png</code></li>
<li><b>Black and white</b>: <code>https://stamen-tiles-{s}.a.ssl.fastly.net/toner/{z}/{x}/{y}{r}.png</code></li>
</ul>
<p>You can browse other free tile layer providers for Leaflet on <a href="https://leaflet-extras.github.io/leaflet-providers/preview/">this link</a>. And of course, if you want to pay there’s a lot of affordable paid tiles out there too.</p>
<h3 id="adding-a-marker">Adding a marker</h3>
<p>Adding a marker is quite straightforward as well. It even looks easier on Leaflet than Google.</p>
<p>Google:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-javascript" data-lang="javascript"><span style="color:#080;font-weight:bold">var</span> marker = <span style="color:#080;font-weight:bold">new</span> google.maps.Marker({
position: <span style="color:#080;font-weight:bold">new</span> google.maps.LatLng(<span style="color:#00d;font-weight:bold">40.7401</span>, -<span style="color:#00d;font-weight:bold">73.9891</span>),
map: map,
title: <span style="color:#d20;background-color:#fff0f0">"End Point Corporation"</span>
});
</code></pre></div><p>Leaflet:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-javascript" data-lang="javascript"><span style="color:#080;font-weight:bold">var</span> marker = <span style="color:#080;font-weight:bold">new</span> L.Marker(<span style="color:#080;font-weight:bold">new</span> L.LatLng(<span style="color:#00d;font-weight:bold">40.7401</span>, -<span style="color:#00d;font-weight:bold">73.9891</span>));
marker.bindPopup(<span style="color:#d20;background-color:#fff0f0">"End Point Corporation"</span>);
map.addLayer(marker);
</code></pre></div><p>And that’s it: we have a working Leaflet map with a marker that displays a text when we click on it.</p>
<p><img src="/blog/2019/03/switching-google-maps-leaflet/leaflet-example-working.jpg" /><br><small>Screenshot of the Leaflet example. Code below, if you want to try it live:</small></p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-html" data-lang="html"><<span style="color:#b06;font-weight:bold">head</span>>
<<span style="color:#b06;font-weight:bold">title</span>>Leaflet map example — End Point Corporation</<span style="color:#b06;font-weight:bold">title</span>>
<<span style="color:#b06;font-weight:bold">script</span> <span style="color:#369">src</span>=<span style="color:#d20;background-color:#fff0f0">"https://unpkg.com/leaflet@1.0.2/dist/leaflet.js"</span>></<span style="color:#b06;font-weight:bold">script</span>>
<<span style="color:#b06;font-weight:bold">link</span> <span style="color:#369">rel</span>=<span style="color:#d20;background-color:#fff0f0">"stylesheet"</span> <span style="color:#369">href</span>=<span style="color:#d20;background-color:#fff0f0">"https://unpkg.com/leaflet@1.0.2/dist/leaflet.css"</span> />
</<span style="color:#b06;font-weight:bold">head</span>>
<<span style="color:#b06;font-weight:bold">body</span>>
<<span style="color:#b06;font-weight:bold">style</span>>
<span style="color:#b06;font-weight:bold">body</span> { <span style="color:#080;font-weight:bold">margin</span>: <span style="color:#00d;font-weight:bold">0</span> };
#<span style="color:#b06;font-weight:bold">map</span> { <span style="color:#080;font-weight:bold">height</span>: <span style="color:#00d;font-weight:bold">100</span><span style="color:#888;font-weight:bold">%</span> };
</<span style="color:#b06;font-weight:bold">style</span>>
<<span style="color:#b06;font-weight:bold">div</span> <span style="color:#369">id</span>=<span style="color:#d20;background-color:#fff0f0">"map"</span>></<span style="color:#b06;font-weight:bold">div</span>>
<<span style="color:#b06;font-weight:bold">script</span> <span style="color:#369">type</span>=<span style="color:#d20;background-color:#fff0f0">"text/javascript"</span>>
<span style="color:#080;font-weight:bold">var</span> endPointLocation = <span style="color:#080;font-weight:bold">new</span> L.LatLng(<span style="color:#00d;font-weight:bold">40.7401</span>, -<span style="color:#00d;font-weight:bold">73.9891</span>);
<span style="color:#080;font-weight:bold">var</span> map = <span style="color:#080;font-weight:bold">new</span> L.Map(<span style="color:#d20;background-color:#fff0f0">"map"</span>, {
center: endPointLocation,
zoom: <span style="color:#00d;font-weight:bold">12</span>,
layers: <span style="color:#080;font-weight:bold">new</span> L.TileLayer(<span style="color:#d20;background-color:#fff0f0">"https://tile.openstreetmap.org/{z}/{x}/{y}.png"</span>)
});
<span style="color:#080;font-weight:bold">var</span> marker = <span style="color:#080;font-weight:bold">new</span> L.Marker(endPointLocation);
marker.bindPopup(<span style="color:#d20;background-color:#fff0f0">"End Point Corporation"</span>);
map.addLayer(marker);
</<span style="color:#b06;font-weight:bold">script</span>>
</<span style="color:#b06;font-weight:bold">body</span>>
</code></pre></div><h3 id="layers-and-controls">Layers and controls</h3>
<p>From this point, we can start doing more complex things if we need to:</p>
<ul>
<li><b>Display images on the map</b>: <a href="https://leafletjs.com/reference-1.4.0.html#imageoverlay">ImageOverlay</a>.</li>
<li><b>Display a custom tile layer</b>: <a href="https://leafletjs.com/reference-1.4.0.html#tilelayer">TileLayer</a>.</li>
<li><b>Draw polygons, rectangles, circles</b>: <a href="https://leafletjs.com/reference-1.4.0.html#polygon">Polygon</a> - <a href="https://leafletjs.com/reference-1.4.0.html#rectangle">Rectangle</a> - <a href="https://leafletjs.com/reference-1.4.0.html#circle">Circle</a>.</li>
<li><b>Display GeoJSON data on the map</b>: <a href="https://leafletjs.com/reference-1.4.0.html#geojson">GeoJSON</a>.</li>
</ul>
<p>You can browse the <a href="https://leafletjs.com/reference-1.4.0.html">Leaflet API reference</a> for further details.</p>
<h3 id="plugins-and-tools">Plugins and tools</h3>
<p>There is some extended functionality in Google Maps that is not available in Leaflet by default unless we use a plugin. For example, if we want to add the “fullscreen” button to the top right corner, just as Google has it, or if we want to let the user draw polygons on top of the map, we’ll need to download and add the reference to the required plugins. Here is a list of the ones I’ve already used:</p>
<ul>
<li><b>“Fullscreen” button plugin</b>: <a href="https://github.com/Leaflet/Leaflet.fullscreen">Leaflet.fullscreen</a>.</li>
<li><b>Vector drawing and editing plugin</b>: <a href="https://github.com/Leaflet/Leaflet.draw">Leaflet.draw</a>.</li>
<li><b>Heatmap plugin</b>: <a href="https://github.com/Leaflet/Leaflet.heat">Leaflet.heat</a>.</li>
</ul>
<p>You can find more plugins at the <a href="https://github.com/Leaflet/">Leaflet GitHub account</a>. And of course, you can (and should!) contribute to improve them.</p>
<p>There is also some alternatives to additional services offered by Google like geocoding or routing. They might have some limitations involved, so it would be wise to take a look at their usage policies first.</p>
<ul>
<li><b>Geocoding API</b>: <a href="https://wiki.openstreetmap.org/wiki/Nominatim">Nominatim</a>.</li>
<li><b>Routing</b>: <a href="http://project-osrm.org/">Project ORSM</a> (free version has limited use).</li>
</ul>
<p>More services can be found at <a href="https://switch2osm.org/other-uses/">switch2osm.org/other-uses</a>.</p>
<h3 id="putting-it-all-together">Putting it all together</h3>
<p>I’ve been using Leaflet for almost a year now in an interactive weather map originally made with the Google Maps API. Of course, I’ve had some minor hiccups along the way, but having full control of the source code and resources allows you to add functionality, fix things or even rewrite whatever you need.</p>
<p>The Leaflet source code is well organized, modularized and easy to understand. I’ve created custom grid layers using different tile sources, with different coordinate systems, animations with frame transitions, custom controls, clickable polygons, popups with dynamic content from AJAX calls, and more. And all works smoothly. So I recommend that you <b>go ahead and start using Leaflet right away</b>.</p>
<p><img src="/blog/2019/03/switching-google-maps-leaflet/leaflet-map-radsat-hd.jpg" /><br><small>Example of a fully-functional Leaflet map with custom controls, overlays, animations and polygons.</small></p>
<p>And this is the repository with my weather map source code: <a href="https://github.com/juanpabloventoso/RadSat-HD">RadSat HD</a>. Feel free to leave any comments or suggestions!</p>