1

I have a fully working map with a few base layers and some buttons, each of which loads and displays different resources. One loads some historical map overlays and uses the L.Control.Opacity plugin https://github.com/dayjournal/Leaflet.Control.Opacity to individually adjust visibility. In the stripped down tester code below I show a single baseLayer and an example overlay OS_1956 in historyMaps, which I hope is sufficient to demonstrate the outline of my scheme.

I'm seeking a generic method to add one or more geoJSON layers into the existing scheme.

One example is to show Roman roads in Britain. The data is available at https://dh.gu.se/dare/ and, as shown, can be very easily added into my map, with opacity control, using the supplied link

roman:  new L.tileLayer("https://dh.gu.se/tiles/imperium/{z}/{x}/{y}.png")

However there are two issues. Firstly it covers the entire Roman empire and has far more detail than I want, but more seriously that detail comes married to an underlying map layer which I really don't want.

The project data is available at https://github.com/klokantech/roman-empire. All I want is (some of) the road data at https://github.com/klokantech/roman-empire/blob/master/data/roads_high.geojson. Clicking on that link displays the roads overlaid on a (different) base layer, while the 'download' button produces a geoJson FeatureCollection which has no reference to that baselayer.
So while it is apparent that L.Control.Opacity can adjust a layer ('roman') made by marrying a geoJSON resource to a tileLayer, I'm afraid the page source code is beyond my grasp and I can't figure out how it is achieved. Yet klokantech (who know a lot more about maps than me) have somehow done it in their git code such that clicking on a link brings up a working map with overlay but downloading the apparent contents makes no mention of that map. I'm a bit stumped. I want to reproduce what they've done, using as a base either a tileLayer of my choosing or an entirely transparent tileLayer.

As an alternative, I have turned a sample from the JSON FeatureCollection into a small js object called rroads, which I can easily add to the map on top of my chosen base layer. var myLayer = L.geoJSON(rroads).addTo(map); However that neither fits my existing scheme nor has opacity control.

I can achieve opacity control using the technique shown at https://stackoverflow.com/questions/47270647/change-opacity-using-leaflet-without-plugin That adds a geoJSON layer called geojson and works, though not as smoothly as I would like, but the control is outside the map area, and doesn't properly integrate with my existing scheme.

I have attempted to add the same data to historyMaps as histjson: new L.geoJson(rroads, {opacity: 0.01 }). That integrates a new L.Control.Opacity slider into my scheme but it doesn't do anything (possibly because L.Control.Opacity relies on .setOpacity which is not available to L.geoJson which for some reason only has .setStyle?)

So I'm stuck. All of my attempts have foundered for different reasons and I don't know what to do next. Can anyone advise on the best way to use L.Control.Opacity with geoJSON data please.

<!DOCTYPE html>
<html>
<head>
    
    <title>Roman</title>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0">

    <link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/leaflet.css" integrity="sha512-xodZBNTC5n17Xt2atTPuE1HxjVMSvLVW9ocqUKLsCC5CXdbqCmblAshOMAS6/keqq/sMZMZ19scR4PsZChSR7A==" crossorigin=""/>
    <script src="https://unpkg.com/[email protected]/dist/leaflet.js" integrity="sha512-XQoYMqMTK8LvdxXYG3nZ448hOEQiglfqkJs1NOQV44cWnUrBc8PkAOcXy20w0vlaXaVUearIOBhiXZ5V3ynxwA==" crossorigin=""></script>
        <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/L.Control.Opacity.min.js"></script>  
        <link  href="https://cdn.jsdelivr.net/npm/[email protected]/dist/L.Control.Opacity.css" rel="stylesheet" > 

            <span id="image-opacity">geojson</span>
            <input type="range" id="sldOpacity" min="0" max="1" step="0.1" value="0.01" />

</head>
<body>

<div id="map" style="width: 600px; height: 400px;"></div>
<script>

// from https://github.com/klokantech/roman-empire/blob/master/data/roads_high.geojson     edited from 850 features to 2
var rroads = {
"type": "FeatureCollection",
"crs": { "type": "name", "properties": { "name": "urn:ogc:def:crs:OGC:1.3:CRS84" } },
"features": [
{ "type": "Feature", "properties": { "Name": "Margary 1c", "descriptio": null }, "geometry": { "type": "LineString", "coordinates": [ [ 0.502122913133777, 51.391440023017289, 0.0 ], [ 0.495790717490887, 51.395644730481983, 0.0 ], [ 0.467996181852347, 51.398570131125844, 0.0 ], [ 0.416152252131068, 51.400357741123933, 0.0 ], [ 0.369580604776158, 51.414122946206994, 0.0 ], [ 0.326046318291103, 51.428705828643693, 0.0 ], [ 0.294349044754911, 51.430916683549633, 0.0 ], [ 0.265543899221985, 51.436409391955181, 0.0 ], [ 0.161395541110144, 51.453685025740953, 0.0 ], [ 0.123662308338893, 51.459592810897803, 0.0 ], [ 0.046184044141974, 51.472842635574253, 0.0 ], [ 0.019196574232533, 51.47698452023878, 0.0 ], [ -0.026549796331548, 51.481223032795768, 0.0 ], [ -0.034092702533585, 51.484955510323758, 0.0 ], [ -0.084812782781212, 51.493732620765158, 0.0 ], [ -0.090320915703155, 51.498060695182005, 0.0 ], [ -0.092991357371235, 51.501225076998104, 0.0 ], [ -0.091134672201963, 51.503997443720763, 0.0 ], [ -0.087451419447726, 51.506807540455391, 0.0 ], [ -0.086412464168425, 51.509372384094299, 0.0 ] ] } },
{ "type": "Feature", "properties": { "Name": "Margary 2d", "descriptio": null }, "geometry": { "type": "LineString", "coordinates": [ [ -0.538186341951123, 53.237073005890736, 0.0 ], [ -0.537926882789907, 53.245346023934971, 0.0 ], [ -0.545858081796996, 53.366221001670091, 0.0 ], [ -0.559928349082197, 53.594268830796381, 0.0 ], [ -0.563521370860075, 53.609530235359955, 0.0 ], [ -0.58216819366816, 53.69286831070356, 0.0 ] ] } },
]
};

    var map = L.map('map').setView([51.505, -0.09], 6);

        const    baseMaps = L.tileLayer('https://api.mapbox.com/styles/v1/{id}/tiles/{z}/{x}/{y}?access_token=pk.eyJ1IjoibWFwYm94IiwiYSI6ImNpejY4NXVycTA2emYycXBndHRqcmZ3N3gifQ.rJcFIG214AriISLbB6B5aw', {
        maxZoom: 18,
        attribution: 'Map data &copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors, ' +
            'Imagery © <a href="https://www.mapbox.com/">Mapbox</a>',
        id: 'mapbox/streets-v11',
        tileSize: 512,
        zoomOffset: -1
    }).addTo(map);

        var geojson = L.geoJson(rroads, {
            style: {
        "opacity": 0.01
                },
    }).addTo(map);

      var historyMaps = {
                    OS_1956:  new L.tileLayer('https://mapseries-tilesets.s3.amazonaws.com/ten_mile/general/{z}/{x}/{y}.png',                                           {   opacity: 0.01, maxNativeZoom: 12}),
                        Roman:  new L.tileLayer("https://dh.gu.se/tiles/imperium/{z}/{x}/{y}.png",                                                                                      { opacity: 0.01, maxNativeZoom: 11      }),
                 histjson:  new L.geoJson(rroads, {opacity: 0.01 }),
        };

      hlay =    L.control.opacity(historyMaps, {collapsed: false, label: "History" } );

        function history_ctl() {
                if (hctl !== 'show') {
                        show_history() 
                        map.on('baselayerchange', function(e) {                                                                 ;   //  say('baselayerchange to ' + e.name);
                                baseMaps[e.name].bringToBack()
                        });
                }                   
                else 
                        lose_history()
        }

        function show_history() {
                         hctl = 'show'                                                                                                                                      ;   //  say('show_history: hctl ', hctl)
                          historyMaps.OS_1956.addTo(map);
                          historyMaps.Roman.addTo(map);
                          historyMaps.histjson.addTo(map);
                                hlay.addTo(map);                                                                                        ;   //  say('map: ', map)
        }
        function lose_history() {
                         hctl = 'hide'                                                                                                                                      ;   //  say('lose_history: hctl ', hctl)
                              historyMaps.OS_1956.removeFrom(map);
                              historyMaps.Roman.removeFrom(map);
                              historyMaps.histjson.removeFrom(map);
                                hlay.remove();
        }

        hctl = 'hide'
        history_ctl();

        L.DomEvent.on(L.DomUtil.get('sldOpacity'), 'change', function () {
                L.DomUtil.get('image-opacity').textContent = this.value;
                geojson.setStyle({
                        opacity: this.value
                });
        });

</script>

</body>
</html>
2
  • If I understand your question correctly, you want to change opacity just for GeoJSON layer?
    – TomazicM
    Commented Jan 20, 2022 at 17:44
  • I want to change the opacity of the GeoJSON layer individually, but within the same History control as the OS_1956 layer.
    – perplexed
    Commented Jan 20, 2022 at 18:04

1 Answer 1

1

As you correctly figured it out, vector layers in Leaflet do not have opacity option to set layer opacity, since opacity is regulated through layer style option. That's the reason why Leaflet.Control.Opacity plugin does not work on these layers.

One possible solution for this is to slightly modify Leaflet.Control.Opacity plugin so that for vector layers function is called which can change vector layer opacity through it's style.

There are only two lines to be added to the plugin internal _addItem method. Below is relevant part of the code where lines have to be inserted:

input.addEventListener('input', (event) => {
  const rgValue = event.target.value;
  const layer = this._getLayer(input.layerId).layer;
  if (this.options.vectorLayerOpacity) {                    // added line
    this.options.vectorLayerOpacity(layer, rgValue / 100);  // added line
  } else if (typeof layer._url === 'undefined') {
  } else {
    layer.setOpacity(Number(rgValue / 100));
  }
});

As a consequence function specified in newly introduced vectorLayerOpacity option will be called at each slider change, with two parameters: layer and opacity.

Below is an example of modified plugin used (only relevant part of code included):

var initLayerOpacity = 0.4;

var initLineOpacity = 1;
var initFillOpacity = 0.7;

var lineOpacity = initLineOpacity * initLayerOpacity;
var fillOpacity = initFillOpacity * initLayerOpacity;

function style(feature) {
  return {
    weight: 2,
    opacity: lineOpacity,
    color: 'white',
    dashArray: '3',
    fillOpacity: fillOpacity,
    fillColor: getColor(feature.properties.density)
  };
}

var geojson = L.geoJson(statesData, {
  style: style,
  opacity: initLayerOpacity
}).addTo(map);

  var layers = {
   'OSM': osmLayer,
   'GeoJSON': geojson
  }
  
function vectorLayerOpacity(layer, opacity) {
  lineOpacity = opacity * initLineOpacity;
  fillOpacity = opacity * initFillOpacity;
  geojson.setStyle(style);
}

L.control.opacity(layers, {
  label: 'Layers Opacity',
  vectorLayerOpacity: vectorLayerOpacity
}).addTo(map);  
1
  • TomazicM, that is fantastic, thankyou so much, it works perfectly with minimal fiddling.
    – perplexed
    Commented Jan 21, 2022 at 7:55

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