Custom component mapbox model update issue

Hello there,

I have been having fun building an app. I have decided to use a custom mapbox to allow a draggable point and boundary to update a row in a table. I have butchered together some code from samples on the retool and mapbox forums.

Currently the map loads the point from the model data. I cannot seem to update the model data on a mapbox event handler.

Model data:

{  boundarydata: {},latitude:{{projectQuery.data.latitude[0]}},longitude:{{projectQuery.data.longitude[0]}} }

iframe code:

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Extract drawn polygon area</title>
<meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no">
<link href="https://api.mapbox.com/mapbox-gl-js/v2.1.1/mapbox-gl.css" rel="stylesheet">
<script src="https://api.mapbox.com/mapbox-gl-js/v2.1.1/mapbox-gl.js"></script>
<style>
body { margin: 0; padding: 0; }
#map { position: absolute; top: 0; bottom: 0; width: 100%; }
</style>
</head>
<body>


<script src="https://api.tiles.mapbox.com/mapbox.js/plugins/turf/v3.0.11/turf.min.js"></script>
<script src="https://api.mapbox.com/mapbox-gl-js/plugins/mapbox-gl-draw/v1.2.1/mapbox-gl-draw.js"></script>
<link rel="stylesheet" href="https://api.mapbox.com/mapbox-gl-js/plugins/mapbox-gl-draw/v1.2.1/mapbox-gl-draw.css" type="text/css">
<div id="map"></div>
<div class="calculation-box">
    <p>Draw a polygon using the draw tools.</p>
    <div id="calculated-area"></div>
</div>

<script>


mapboxgl.accessToken = 'tokencode';

var draw = new MapboxDraw({
        displayControlsDefault: false,
        controls: {
            polygon: true,
            trash: true
        }
    });
var marker1 = new mapboxgl.Marker({
    draggable: true,
    color: "red"
});

function updateData(e) {
  var data = draw.getAll();
  var lat = marker1._lngLat.lat;
  var lng = marker1._lgnLat.lng;
  window.Retool.modelUpdate({ boundarydata:data, latitude:lat, longitude:lng });
}
                        
var map = new mapboxgl.Map({
  container: 'map', // container id
  style: 'mapbox://styles/mapbox/dark-v10', //hosted style id
  zoom: 15 // starting zoom
  });

    
map.addControl(draw);
map.on('draw.create', updateData);
map.on('draw.delete', updateData);
map.on('draw.update', updateData);
map.on('marker1.update', updateData);


window.Retool.subscribe(function(model) {
              if (!model) {
                return;
              }
    map.setCenter([parseFloat(model.longitude),parseFloat(model.latitude)]);
  
    marker1.setLngLat([parseFloat(model.longitude),parseFloat(model.latitude)])
    marker1.addTo(map);
});



</script>

</body>
</html>

If I create a button to run a query to update the table the new values are not inserted or updated. If I try and create an action to update a text box similarly the new model data is not updated when accessed from the component e.g. {{customMapbox2.model.latitude}}.

Also the issue is that the boundary polygon wont finish on doubleclick like the tutorial.

I used the javascript implemntation window/model mentioned here.

Any help or ideas would be welcome.

Had a go. Marker needs a seperate marker1.on dragend event, I added its own function as well. See mapbox tutorial.

Updated code:

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Extract drawn polygon area</title>
<meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no">
<link href="https://api.mapbox.com/mapbox-gl-js/v2.1.1/mapbox-gl.css" rel="stylesheet">
<script src="https://api.mapbox.com/mapbox-gl-js/v2.1.1/mapbox-gl.js"></script>
<style>
body { margin: 0; padding: 0; }
#map { position: absolute; top: 0; bottom: 0; width: 100%; }
</style>
</head>
<body>


<script src="https://api.tiles.mapbox.com/mapbox.js/plugins/turf/v3.0.11/turf.min.js"></script>
<script src="https://api.mapbox.com/mapbox-gl-js/plugins/mapbox-gl-draw/v1.2.1/mapbox-gl-draw.js"></script>
<link rel="stylesheet" href="https://api.mapbox.com/mapbox-gl-js/plugins/mapbox-gl-draw/v1.2.1/mapbox-gl-draw.css" type="text/css">
<style>
.coordinates {
background: rgba(0, 0, 0, 0.5);
color: #fff;
position: absolute;
bottom: 20px;
left: 10px;
padding: 5px 10px;
margin: 0;
font-size: 11px;
line-height: 18px;
border-radius: 3px;
display: none;
}
</style>
 
<div id="map"></div>
<pre id="coordinates" class="coordinates"></pre>

<script>


mapboxgl.accessToken = 'token';

var draw = new MapboxDraw({
        displayControlsDefault: false,
        controls: {
            polygon: true,
            trash: true
        }
    });
var marker1 = new mapboxgl.Marker({
    draggable: true,
    color: "red"
});

function updateData() {
  var dataPts = draw.getAll();
  window.Retool.modelUpdate({boundarydata:dataPts});    
}

function onDragEnd() {
  const lngLat = marker1.getLngLat();
  coordinates.style.display = 'block';
  coordinates.innerHTML = `Longitude: ${lngLat.lng}<br />Latitude: ${lngLat.lat}`;
  window.Retool.modelUpdate({latitude:lngLat.lat,longitude:lngLat.lng});
}
 
                        
var map = new mapboxgl.Map({
  container: 'map', // container id
  style: 'mapbox://styles/mapbox/dark-v10', //hosted style id
  zoom: 15 // starting zoom
  });

  
map.addControl(draw);
map.on('draw.create', updateData);
map.on('draw.delete', updateData);
map.on('draw.update', updateData);



window.Retool.subscribe(function(model) {
              if (!model) {
                return;
              }
    map.setCenter([parseFloat(model.longitude),parseFloat(model.latitude)]);
  
    marker1.setLngLat([parseFloat(model.longitude),parseFloat(model.latitude)])
    marker1.addTo(map);
  
     
});

marker1.on('dragend', onDragEnd);

</script>

</body>
</html>

Once I have got the draw boundary to load presaved points I will update.

Hi @ivthecat Thanks for sharing this! I imported it into my own app, and I see that the model gets updated correctly now. It also seems like the boundary polygon does finish on double click now.

I'm not sure if I'm understanding what you're working on with the draw boundary. Are you looking to start with a certain polygon on load? Keep me posted!

Hello Tess,

I will get back onto working with the presaved points. Its a string that is then split into a list of points when the page is loaded.

@ivthecat Keep me posted on how I can help! :slightly_smiling_face:

Hello I have been working on the custom iframe map. MapBox has poor quality satellite underlays at the level I require so I resorted to Google Maps API.

Its working with a movable marker and redline boundary. On load it will draw the marker and boundary if not null in the query otherwise a default zoom on a location without marker and boundary.

There is currently a bug in that if drawing a new boundary the existing one remains on the screen until it is reloaded.

I have tried numerous ways to add polygons to lists and then set old ones to null but to no avail.

Its quite hard to debug the custom comonent with no console.log. I can't seem to see it in the chrome inspection. If there is a tutorial that would be great.

The coordinates are from an SQL query and then passed through a javascript transformer returning [] or JSON.parse(stringCoords). The transformer.value is in the custom component model. The latitude and longitude are also part of the model.
Code below:

<!DOCTYPE html>
<html>
<body>
<div id="googleMap" style="width:100%;height:100%;"></div>
<script>

var zoomStart = 19;


var polyList = [];
projectPoly = null;

var polyOptions = { strokeColor: "red",
                    strokeWeight: 4,
                    fillOpacity: 0,
                    editable: true,
                    draggable: true
                };

function updateMarker(marker){
   google.maps.event.addListener(marker,'dragend',function() {
      window.Retool.modelUpdate({latitude: this.getPosition().lat(),
                                longitude: this.getPosition().lng()});  
    });
};

function updateBoundary(poly){
    window.Retool.modelUpdate({boundarydata:JSON.stringify(poly.getPath().getArray())});
    google.maps.event.addListener(poly.getPath(),'set_at',function() {
      window.Retool.modelUpdate({boundarydata:JSON.stringify({coords:poly.getPath().getArray()})});  
    });
    google.maps.event.addListener(poly.getPath(),'insert_at',function() {
      window.Retool.modelUpdate({boundarydata:JSON.stringify({coords:poly.getPath().getArray()})});  
    });
    google.maps.event.addListener(poly.getPath(),'remove_at',function() {
      window.Retool.modelUpdate({boundarydata:JSON.stringify({coords:poly.getPath().getArray()})});  
    });
};

function myMap() {
  window.Retool.subscribe(function(model) {
                if (!model) {
                  return;
                }
      if (model.latitude!=null){
        lat = model.latitude;
        lng = model.longitude;
        showMarker = true;
        zoomLevel = 19;
      } else{
        lat = 51.3321548;
        lng = 1.4177604;
        showMarker = false;
        zoomLevel =15;
      }
      if (model.boundarydata!=null){
        polyCoords = model.boundarydata;
        polyShow = true;
      };
  });

var mapProp= {
  center:new google.maps.LatLng(lat,lng),
  zoom:zoomLevel,
  mapTypeId: google.maps.MapTypeId.SATELLITE,
  disableDefaultUI: true,
  zoomControl: true
};
var map = new google.maps.Map(document.getElementById("googleMap"),mapProp);

    drawingManager = new google.maps.drawing.DrawingManager({
          drawingControlOptions: {
            position: google.maps.ControlPosition.TOP_RIGHT,
            drawingModes: [
                  google.maps.drawing.OverlayType.MARKER,
                  google.maps.drawing.OverlayType.POLYGON]
          },
          markerOptions:polyOptions,
          polygonOptions:polyOptions
    });
    drawingManager.setMap(map);
    

  
  const projectMarker = new google.maps.Marker({position:{lat:lat,lng:lng},draggable:true});
 
  
  if (showMarker){
    projectMarker.setMap(map)
    updateMarker(projectMarker);
   
  };
  
  if (polyShow){
    const projectPolyOpts = structuredClone(polyOptions);
    projectPolyOpts.paths = polyCoords.coords;
    const projectPoly = new google.maps.Polygon(projectPolyOpts);
    projectPoly.setMap(map);
    //var infoWindow = new        google.maps.InfoWindow({content:JSON.stringify(polyCoords)}); // useful to debug.
    //infoWindow.open(map,projectMarker);
    updateBoundary(projectPoly);
    polyList.push("projectPoly"); //todo adding the google object to a list breaks the script. 
    }
  
  google.maps.event.addListener(drawingManager, 'overlaycomplete', function (e) {
                    if (e.type == google.maps.drawing.OverlayType.MARKER) {
                          google.maps.event.clearListeners(projectMarker,'dragend');
                          updateMarker(e.overlay);
                          projectMarker.setMap(null);
                          projectMarker = e.overlay;
                          projectMarker.setMap(map);
                          drawingManager.setDrawingMode(null);
                        }
                    if (e.type == google.maps.drawing.OverlayType.POLYGON) {
                          polyList.push("e.overlay"); //todo I was going to then set polyList[0] = null... to delete old polygon.
                          updateBoundary(e.overlay);
                          projectPoly.setMap(null);
                          projectPoly = e.overlay;
                          drawingManager.setDrawingMode(null);
                    };
                   
                    });

};



</script>

<script src="https://maps.googleapis.com/maps/api/js?key=yourGoogleMapsAPIkey&libraries=drawing&callback=myMap"></script>

</body>
</html>

Note you need a google maps API key from google.

An external button can then be added to save / update the query of the updated model information to the database.

Its a good idea for the main query to have a success trigger to reload the custom component if the page is used over multiple records.

Screenshot 2023-03-05 124207

Hi @ivthecat,

Thanks for the update!

Where are you attempting to console.log (and where)? It may depend on what data you're logging, but I see this console log in the Chrome dev tools (it doesn't show up in the Retool debugger)

I found an external post with a similar use case here. :crossed_fingers: Hope it helps: https://stackoverflow.com/questions/14166546/google-maps-drawing-manager-limit-to-1-polygon