Leaflet – Create HTML Webmap with ESRI JavaScript API and Leaflet

arcgis-maps-sdk-javascriptleafletweb-mapping

I have created an ESRI JavaScript based map. It is reading in a webmap from my ArcGIS Online account and works as is required.

I would like to include some additional context to my webmap through the addition of a map showing the data from the Windy API. The Windy API is Leaflet based, and from what I can tell I cannot add the Windy data to my existing ESRI Webmap.

I thought that having an inset map shown next to or somewhere in my existing webmap (in another div) might be an option.

I have tried various ways of doing this, but get a number of console errors and I cannot get both maps to load at the same time.

Here are some examples:

    <!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Linked Maps Example</title>
  <!-- Include ESRI JavaScript API -->
  <link rel="stylesheet" href="https://js.arcgis.com/4.23/esri/themes/light/main.css">
  <script src="https://js.arcgis.com/4.23/"></script>

  <!-- Include Leaflet and Leaflet.Sync plugin -->
    <script src="https://unpkg.com/[email protected]/dist/leaflet.js"></script>
    <script src="https://api.windy.com/assets/map-forecast/libBoot.js"></script>        
    <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script> 
    <link href="https://refreshless.com/nouislider/documentation/assets/base.css?v=4" rel="stylesheet">
    <link href="https://refreshless.com/nouislider/documentation/assets/prism.css" rel="stylesheet">  
    <link href="https://refreshless.com/nouislider/dist/nouislider.css?v=1560" rel="stylesheet">
    <script src="https://refreshless.com/nouislider/dist/nouislider.js?v=1560"></script>
    <script src='https://unpkg.com/@turf/turf@6/turf.min.js'></script>
    <script src='https://unpkg.com/@mapbox/leaflet-pip@latest/leaflet-pip.js'></script>


  <style>
    #map1, #map2 {
      height: 50vh;
      width: 50%;
      float: left;
    }
  </style>
</head>
<body>
  <div id="map1"></div>
  <div id="map2"></div>

  <script>
    // ESRI JavaScript API Map
    const esriMap = new Map({
      basemap: "streets",
      ground: "world-elevation"
    });

    const esriMapView = new MapView({
      container: "map1",
      map: esriMap,
      zoom: 10,
      center: [-118.2437, 34.0522]
    });

    // Leaflet Map
    const leafletMap = L.map('map2').setView([34.0522, -118.2437], 10);
    L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png').addTo(leafletMap);

    // Create a linked map event
    esriMapView.watch("extent", (newExtent) => {
      const newCenter = esriMapView.center;
      const newZoom = esriMapView.zoom;
      leafletMap.setView([newCenter.latitude, newCenter.longitude], newZoom);
    });

    leafletMap.on("moveend", () => {
      const newCenter = leafletMap.getCenter();
      const newZoom = leafletMap.getZoom();
      esriMapView.center = [newCenter.lat, newCenter.lng];
      esriMapView.zoom = newZoom;
    });
  </script>
</body>
</html>

With this example, I received the following errors:

  1. Uncaught ReferenceError: L is not defined

  2. Uncaught TypeError: object is not iterable (cannot read property
    Symbol(Symbol.iterator))
    at new Map ()
    at Esri_leaf.html:37:21 (anonymous) @ Esri_leaf.html:37

A second attempt:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Linked Maps Example</title>
  <!-- Include ESRI JavaScript API -->
  <link rel="stylesheet" href="https://js.arcgis.com/4.23/esri/themes/light/main.css">
  <script src="https://js.arcgis.com/4.23/"></script>

  <!-- Include Leaflet -->
  <link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/leaflet.css">
  <script src="https://unpkg.com/[email protected]/dist/leaflet.js"></script>

  <style>
    #map1, #map2 {
      height: 50vh;
      width: 50%;
      float: left;
    }
  </style>
</head>
<body>
  <div id="map1"></div>
  <div id="map2"></div>

  <script>
    require([
      "esri/Map",
      "esri/views/MapView",
      "dojo/domReady!"
    ], function(Map, MapView) {
      // ESRI JavaScript API Map
      const esriMap = new Map({
        basemap: "streets"
      });

      const esriMapView = new MapView({
        container: "map1",
        map: esriMap,
        zoom: 10,
        center: [-118.2437, 34.0522]
      });

      // Leaflet Map
      const leafletMap = L.map('map2').setView([34.0522, -118.2437], 10);
      L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png').addTo(leafletMap);

      // Create a linked map event
      esriMapView.watch("extent", (newExtent) => {
        const newCenter = esriMapView.center;
        const newZoom = esriMapView.zoom;
        leafletMap.setView([newCenter.latitude, newCenter.longitude], newZoom);
      });

      leafletMap.on("moveend", () => {
        const newCenter = leafletMap.getCenter();
        const newZoom = leafletMap.getZoom();
        esriMapView.center = [newCenter.lat, newCenter.lng];
        esriMapView.zoom = newZoom;
      });
    });
  </script>
</body>
</html>

Gives me a multiple definition error.

Is what I am trying to do even possible? Is it possible to have two maps in different frames one ESRI JavaScript API based and the other Leaflet based on a single page and have them interact with each other?

Best Answer

Your first example cannot work because it does not use ESRI JavaScript API in the right way.

In the second example there several things to be corrected. First the order of including ESRI and Leaflet libraries has to be reversed:

<!-- Include Leaflet -->
<link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/leaflet.css">
<script src="https://unpkg.com/[email protected]/dist/leaflet.js"></script>

<!-- Include ESRI JavaScript API -->
<link rel="stylesheet" href="https://js.arcgis.com/4.23/esri/themes/light/main.css">
<script src="https://js.arcgis.com/4.23/"></script>

Second thing is that the way map panning/zooming events are handled now it would go into the infinite loop. You have to check not to pan/move the other map when pan/move was triggered from that map.

Third thing is that ESRI APIs expect [lng, lat] order for coordinate input, unlike Leaflet APIs which expects [lat, lng] order.

So relevant part of the code could finally look something like this (tested):

var esriMove = false;
var leafletMove = false;
var nEsri = 0;
var nLeaflet = 0;

esriMapView.watch("extent", (newExtent) => {
  const newCenter = esriMapView.center;
  const newZoom = esriMapView.zoom;
  ++nEsri;
  if (!leafletMove) {
    esriMove = true;
    leafletMap.setView([newCenter.latitude, newCenter.longitude], newZoom);
    }
  else if (nEsri == nLeaflet) {
    leafletMove = false;
  }
});

leafletMap.on("moveend", () => {
  const newCenter = leafletMap.getCenter();
  const newZoom = leafletMap.getZoom();
  ++nLeaflet;
  if (!esriMove) {
    leafletMove = true;
    esriMapView.center = [newCenter.lng, newCenter.lat];
    esriMapView.zoom = newZoom;
    }
  else if (nEsri == nLeaflet) {
    esriMove = false;
  }
});
Related Question