0

Can you give me a solution to be able to display two location pickers using leaflet map. The code for the first map is as below. I have tried various ways to duplicate it, but it always ends with some functions not working.

<head>
    <link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/leaflet.css">
</head>
<body>

  <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.3.4/leaflet.js"></script>

  <style>
    #map {
      * + * {
        margin:0;
      }
    }
  </style>

  <p2></p2>

  <script>

    var map;
    var pin;
    var tilesURL='https://{s}.tile.openstreetmap.fr/hot/{z}/{x}/{y}.png';
    var mapAttrib='&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>, Tiles courtesy of <a href="http://hot.openstreetmap.org/" target="_blank">Humanitarian OpenStreetMap Team</a>';

    // add map container
    $('p2').prepend('<div id="map" style="height:300px;width:100%;"></div>');
    MapCreate();

    function MapCreate() {
      // create map instance
      if (!(typeof map == "object")) {
        map = L.map('map', {
          center: [-6.175,106.827],
          zoom: 8
        });
      }
      else {
        map.setZoom(8).panTo([-6.175,106.827]);
      }
      // create the tile layer with correct attribution
      L.tileLayer(tilesURL, {
        attribution: mapAttrib,
        maxZoom: 19
      }).addTo(map);
    }

    map.on('click', function(ev) {
      $('#acff-post-field_66575345d723e').val(ev.latlng.lat+', '+ev.latlng.lng);
      $('#lng').val(ev.latlng.lng);
      if (typeof pin == "object") {
        pin.setLatLng(ev.latlng);
      }
      else {
        pin = L.marker(ev.latlng,{ riseOnHover:true,draggable:true });
        pin.addTo(map);
        pin.on('drag',function(ev) {
          $('#acff-post-field_66575345d723e').val(ev.latlng.lat+', '+ev.latlng.lng);
          $('#lng').val(ev.latlng.lng);
        });
      }
    });

  </script>

</body>

1 Answer 1

0

If you need to have two different maps for each marker, you need to initialize two maps. There are similar StackOverflow questions: here and here. There is also a Leaflet example here using two maps on the same page.

Here is a very simple example creating two pickers on one page using a custom MapPicker class:

class MapPicker {

  constructor(pickerId) {
    this.pickerId = pickerId;
    this._init();
  }

  _init() {

    this.pickerElement = document.getElementById(this.pickerId);
    this.pickerElement.classList.add('map-picker');

    this.mapElement = document.createElement('div');
    this.mapElement.classList.add('map');

    this.inputElement = document.createElement('input');
    this.inputElement.setAttribute('type', 'text');

    this.pickerElement.append(this.mapElement, this.inputElement);

    this.map = L.map(this.mapElement).setView([-6.175, 106.827], 8);

    L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
        maxZoom: 19,
        // add a link to OpenStreetMap (omitted here for shorter line width)
        attribution: '&copy; OpenStreetMap contributors'
      })
      .addTo(this.map);

    this.marker = null;

    this.map.on('click', (e) => {

      if (this.marker != null) {
        this.latlng = e.latlng;
      }
      else {
        this.marker = L.marker(e.latlng, {
          id: `${this.pickerId}_marker`,
          riseOnHover: true,
          draggable: true,
        });

        this.map.addLayer(this.marker);

        this.marker.on('drag', (e) => {
          this.inputElement.value = `${e.latlng.lat}, ${e.latlng.lng}`;
        });
      }

      this.inputElement.value = `${e.latlng.lat}, ${e.latlng.lng}`;

    });

  }

  set latlng(latlng) {
    this.marker.setLatLng(latlng);
    this.inputElement.value = `${latlng.lat}, ${latlng.lng}`;
  }

  get latlng() {
    return this.marker.getLatLng();
  }

  clear() {
    this.marker.removeFrom(this.map);
    this.marker = null;
    this.inputElement.value = null;
  }

}

const picker1 = new MapPicker('picker1');
const picker2 = new MapPicker('picker2');
.map-picker {
  display: block;
  margin-bottom: 2rem;
}

.map-picker .map {
  height: 150px;
  margin-bottom: 1rem;
}
<!DOCTYPE html>
<html lang="en">

<head>

  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Leaflet Example</title>

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

</head>

<body>

  <div id="picker1"></div>
  <div id="picker2"></div>

</body>


I am not sure where the input field with the ID acff-post-field_66575345d723e (in your example code) comes from. With some modification, you can also use existing input fields:

class MapPicker {

  constructor(pickerId, inputElement=null) {

    this.pickerId = pickerId;
    this.inputElement = inputElement;

    this._init();

  }

  _init() {

    this.pickerElement = document.getElementById(this.pickerId);
    this.pickerElement.classList.add('map-picker');

    this.mapElement = document.createElement('div');
    this.mapElement.classList.add('map');

    if (!this.inputElement) {
    
      this.inputElement = document.createElement('input');
      this.inputElement.setAttribute('type', 'text');

      this.pickerElement.append(this.mapElement, this.inputElement);
      
    }
    else {
      this.pickerElement.append(this.mapElement);
    }

    this.map = L.map(this.mapElement).setView([-6.175, 106.827], 8);

    L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
      maxZoom: 19,
      // add a link to OpenStreetMap (omitted here for shorter line width)
      attribution: '&copy; OpenStreetMap contributors'
    })
    .addTo(this.map);

    this.marker = null;

    this.map.on('click', (e) => {

      if (this.marker != null) {
        this.latlng = e.latlng;
      }
      else {
        this.marker = L.marker(e.latlng, {
          id: `${this.pickerId}_marker`,
          riseOnHover: true,
          draggable: true,
        });

        this.map.addLayer(this.marker);

        this.marker.on('drag', (e) => {
          this.inputElement.value = `${e.latlng.lat}, ${e.latlng.lng}`;
        });
      }

      this.inputElement.value = `${e.latlng.lat}, ${e.latlng.lng}`;

    });

  }

  set latlng(latlng) {
    this.marker.setLatLng(latlng);
    this.inputElement.value = `${latlng.lat}, ${latlng.lng}`;
  }

  get latlng() {
    return this.marker.getLatLng();
  }

  clear() {
    this.marker.removeFrom(this.map);
    this.marker = null;
    this.inputElement.value = null;
  }

}

const picker1 = new MapPicker('picker1', 
   document.getElementById('acff-post-field_66575345d723e'));
const picker2 = new MapPicker('picker2', document.getElementById('input2'));
.map-picker {
  display: block;
  margin-bottom: 2rem;
}

.map-picker .map {
  height: 150px;
  margin-bottom: 1rem;
}
<!DOCTYPE html>
<html lang="en">

<head>

  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Leaflet Example</title>

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

</head>

<body>

  <input id="acff-post-field_66575345d723e" type="text">
  <input id="input2" type="text">

  <div id="picker1"></div>
  <div id="picker2"></div>
  
</body>


Below my old answer thinking that you need two markers on one map. I didn't read the title just your description at first.

There are great tutorials on leafletjs.com how to use Leaflet. Unless there is a very good reason to use an old version of Leaflet, go with the most current version 1.9.4 as of June 11, 2024. Before I look at your issue, let's first clean up your code a little bit. (Please note that I am using vanilla JavaScript and not jQuery.)

<!DOCTYPE html>
<html lang="en">
<head>

  <!-- ... --> 

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

  <style>
    #map { height: 300px; }
  </style>

</head>
<body>

  <div id="map"></div>

  <script>
    const map = L.map('map').setView([-6.175, 106.827], 8);

    const basemap = L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
      maxZoom: 19,
      // add a link to OpenStreetMap (omitted here for shorter line width)
      attribution: '&copy; OpenStreetMap contributors',
    });
    basemap.addTo(map);
  </script>
    
</body>
</html>

This example shows a simple basemap.

I assume that there is somewhere an input field with the ID acff-post-field_66575345d723e. Let's add simple input fields for this example. I am choosing another ID (location1 for the first input field, and location2 for the second) to simplify the code:

<input type="text" name="location1" id="location1">
<input type="text" name="location2" id="location2">

Next, we'll add a L.layerGroup for the marker(s):

const markerLayer = L.layerGroup();
markerLayer.addTo(map);

Next, we want to add pins to the map, you'll need to listen to a click event as you already figured that out.

map.on('click', function(e) {
  // ...
});

Now, we add the a marker when a click event occurs. If I understand your question correctly, you want maximum 2 pins on the map.

map.on('click', (e) => {

  const pinNumber = markerLayer.getLayers().length + 1;

  if (pinNumber > 2) {
    return;  // no additional markers allowed
  }

  const inputElement = document.getElementById(`location${pinNumber}`);

  const marker = L.marker(e.latlng, {
    id: pinNumber,
    title: `Pin ${pinNumber}`,
    riseOnHover: true, 
    draggable: true,
  });

  markerLayer.addLayer(marker);

  inputElement.value = `${e.latlng.lat}, ${e.latlng.lng}`;

});

After a second marker is added to the map, no additional markers will be added.

Finally, as you also figured out, you'll attach a drag event listener to the marker:

map.on('click', (e) => {

  // ...

  markerLayer.addLayer(marker);

  marker.on('drag', (e) => {
    inputElement.value = `${e.latlng.lat}, ${e.latlng.lng}`;
  });

  inputElement.value = `${e.latlng.lat}, ${e.latlng.lng}`;

});

Here is a runnable example:

const map = L.map('map').setView([-6.175, 106.827], 8);

L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
  maxZoom: 19,
  attribution: '&copy; OpenStreetMap contributors',  // add a link
}).addTo(map);

const markerLayer = L.layerGroup().addTo(map);

map.on('click', (e) => {

  const pinNumber = markerLayer.getLayers().length + 1;

  if (pinNumber > 2) { return; }

  const inputElement = document.getElementById(`location${pinNumber}`);

  const marker = L.marker(e.latlng, {
    id: pinNumber,
    title: `Pin ${pinNumber}`,
    riseOnHover: true,
    draggable: true,
  });

  markerLayer.addLayer(marker);

  marker.on('drag', (e) => {
    inputElement.value = `${e.latlng.lat}, ${e.latlng.lng}`;
  });

  inputElement.value = `${e.latlng.lat}, ${e.latlng.lng}`;

});
<!DOCTYPE html>
<html lang="en">

<head>

  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Leaflet Example</title>

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

  <style>
    #map {
      height: 150px;
      margin-bottom: 1rem;
    }
  </style>

</head>

<body>

  <div id="map"></div>

  <input type="text" name="location1" id="location1">
  <input type="text" name="location2" id="location2">

</body>

</html>

Not the answer you're looking for? Browse other questions tagged or ask your own question.