Home > OS >  How can I draw a Polygon on the Gmap without refreshing the whole Gmap component?
How can I draw a Polygon on the Gmap without refreshing the whole Gmap component?

Time:07-05

I'm developing an app with JSF and PrimeFaces. I have a PrimeFaces Gmap component on a page. I want to allow the user to draw a map polygon on screen, simply clicking each vertex, and see how the polygon constructs on the fly with each click. And also receive each point data in the Backing Bean, so I can store/persist the data in the database.

I followed the logic "For each click I want to update the map, and the model in the backing bean", so I did put update="@this" in the <p:ajax> tag in the <p:gmap> component. Problem is, making so, the whole Gmap component totally reloads, and loses its position, zoom and center. Resetting to its init values. This makes the draw operation extremely problematic because user has to zoom and pan to the zone where the polygon is wanted to be drawn, with each click.

I need the Gmap to update without totally reload it. I know the solution involves JS, sadly I'm not very expert in that language.

I made a Minimal reproducible example.

JSF page:

    <h:head>
        <script type="text/javascript" src="https://maps.google.com/maps/api/js?sensor=true"></script>
    </h:head>
    <h:body>
        <h:form id="frmMap2"> 
            <p:gmap center="41.381542, 2.122893" 
                    zoom="15" 
                    type="HYBRID" 
                    style="width:100%;height:400px"
                    model="#{testMapBean.polygonModel}">
                   <p:ajax event="pointSelect" 
                           listener="#{testMapBean.onPointSelect}" 
                           update="@this"/>
            </p:gmap>
        </h:form>
    </h:body>

The backing bean (View Scoped):

@Named(value = "testMapBean")
@ViewScoped
public class TestMapBean implements Serializable {

    private MapModel polygonModel;
    
    private Polygon polygon;
    
    public TestMapBean() 
    {
        
    }

    @PostConstruct
    public void init()
    {
        polygonModel = new DefaultMapModel();
 
        polygon = new Polygon();
        polygon.setStrokeColor("#FF9900");
        polygon.setFillColor("#FF9900");
        polygon.setStrokeOpacity(0.7);
        polygon.setFillOpacity(0.7);

        polygonModel.addOverlay(polygon);
    }
     
    public void onPointSelect(PointSelectEvent event) 
    {
        LatLng latlng = event.getLatLng();
        polygon.getPaths().add(latlng);
        
    }
    // public getters and setters omitted for brevity...

I'm using: JSF 2.3 (mojarra), Primefaces 10.0, Wildfly 26.1.

EDIT

Finally made it., for anyone interested, this is the code:

View:

<h:head>
        <script type="text/javascript" 
                src="https://maps.google.com/maps/api/js?sensor=true"></script>
        
        <script type="application/javascript">
            var map;
            var pl;
             
            function initbs()
            { 
                // Get map through the PF widget
                map = PF('wmaptest').map;
                 
                // Create Polygon 
                pl = new google.maps.Polygon({ 
                    strokeColor: "#777777",
                    strokeOpacity: 0.8,
                    strokeWeight: 2,
                    fillColor: "#FF0000",
                    fillOpacity: 0.35,
                }); 
            }
                
            // Have a function to set coordinates to the line
            function drline(coords) 
            {  
                // Add the (empty) line to the map
                pl.setMap(map);  
                pl.setPath(coords);
            }
            
        </script>
    </h:head>
    <h:body>
        <h:form id="frmMap2"> 
            <p:gmap widgetVar="wmaptest"
                    center="41.381542, 2.122893" 
                    zoom="15" 
                    type="ROADMAP" 
                    style="width:100%;height:800px"> 
                     
                    <p:ajax event="pointSelect" 
                            listener="#{testMapBean.onPointSelect}"/>  
            </p:gmap>
        </h:form> 
    </h:body>

Bean:

@Named(value = "testMapBean")
@ViewScoped
public class TestMapBean implements Serializable {

    private Set<LatLng> latLngList = new HashSet<LatLng>();
     
    public TestMapBean() 
    {
    }

    @PostConstruct
    public void init()
    {
        PrimeFaces.current().executeInitScript("initbs()");
    }
      
    public void drawLine()
    { 
        String str = latLngList.stream()
                        .map(this::toJavaScript)
                        .collect(Collectors.joining(",", "drline([", "])"));
        
        PrimeFaces.current().executeScript(str); 
    }

    public String toJavaScript(LatLng latLng) {
        return "{lat:"   latLng.getLat()   ",lng:"   latLng.getLng()   "}";
    }
    
    public void onPointSelect(PointSelectEvent event) 
    {
        LatLng latlng = event.getLatLng();
        latLngList.add(latlng);
        drawLine();
    }
    
    public Set<LatLng> getLatLngList() {
        return latLngList;
    }

    public void setLatLngList(Set<LatLng> latLngList) {
        this.latLngList = latLngList;
    }
}

CodePudding user response:

I did something similar which you can use here as well. In my case I have a set of markers which you can select a few of to plan appointments at the selected markers.

Basically I added an empty Polyline (you can do something similar using a Polygon), and a function to add coordinates to that line at the client side like:

// Get map through the PF widget
let map = PF('yourWidgetVarName').map;
// Create Polyline
let pl = new google.maps.Polyline({
  geodesic: true,
  strokeColor: '#ff0000',
  strokeOpacity: 1,
  strokeWeight: 2
});
// Add the (empty) line to the map
pl.setMap(map);
// Have a function to set coordinates to the line
function line(coords) {
  pl.setPath(coords);
}

See also:

And in the backend I filter my markers to the selected set, get their lat long position and transform them to a JavaScript array and call the line function to draw them on the map. Instead of markers you will probably just have a list of LatLng objects. So you can probably do:

public void drawLine() {
  PrimeFaces.current().executeScript(
          latLngList.stream()
                  .map(this::toJavaScript)
                  .collect(Collectors.joining(",", "line([", "])"))
  );
}

public String toJavaScript(LatLng latLng) {
  return "{lat:"   latLng.getLat()   ",lng:"   latLng.getLng()   "}";
}
  • Related