Which GIS tools do you use and why?

just curious… it seems like people here use all sorts of different GIS tools. which one(s) do you use and why?

from Wikipedia: “A geographic information system ( GIS ) is a system designed to capture, store, manipulate, analyze, manage, and present spatial or geographic data.”

the ones i use most often are ArcGIS Online (because it’s easy-to-use, widely-used, free for a basic personal account, allows for easy publication of maps, is cloud-based so can be used from any device, and has a nice Story Maps feature), QGIS (because it’s free but relatively powerful, widely used, and seems to be relatively light on computer resources as far as desktop GIS applications go), and the PowerMaps plugin for Excel (because it’s included in some versions of Excel, like the one i have).

1 Like

I use R. If you learn some basic R commands you can do everything you need in a GIS with R. It’s free and constantly being upgraded and it runs on linux, mac and windows. It definitely takes a more time to get familiar with than ArcGIS but in the long run it’s worth it. It can be frustrating to learn because everything is done with code, you don’t get a clickable interface. But saving your code in “scripts” makes it much easier to reproduce and build on your previous work than working with ArcMap.

Here’s a new online, open source book on using R as a GIS:

https://geocompr.robinlovelace.net/

R is widely used by ecologists so if you want to map species records, make species distribution models with latest statistical tools, etc. you can usually find a script online that will show you how to do it. Here’s a section on using R for ecology in the book above:

https://geocompr.robinlovelace.net/eco.html

There are new ways to make interactive maps with R and this is an exciting and rapidly improving area within R programming. Check this out:

Using plotly: https://plot.ly/r/maps/

Using leflet: https://www.earthdatascience.org/courses/earth-analytics/get-data-using-apis/leaflet-r/

You can also use Python as a powerful GIS and make awesome maps. In my opinion Python is a bit more difficult to quickly get up and running than R.

QGIS, as you mentioned, is free and I know serious GIS people who greatly prefer it to ESRI ArcMap.

I’m a bit of an evangelist for free open source software so apologies for my enthusiastic response to your post! Millions of dollars flow to ESRI (the ArcMap company) each year from universities, non-profits and government for software licenses. In my opinion, they could be getting a much better GIS software for free if they used R, Python, or QGIS.

7 Likes

I use ArcGIS because it’s what I have at work :) It’s a powerful tool, but also buggy, slow, and expensive. Thus I only use it for work stuff, which overlaps with some, but not all of my iNaturalist activity.

At home, i have a pretty junky laptop so my most advanced form of mapping software is actually iNaturalist itself :)

1 Like

I have institutional access to software and I still choose to use QGIS, R, and Python because I think it’s important to use free and open source software for a variety of reasons. Not least of which is that I won’t necessarily have “free” access forever and I don’t want to be tied into their systems.

3 Likes

I think to a large degree this depends on if you have experience programming in other languages. If you do have that experience, then you will likely find Python easier to learn, as it at least will feel familiar in terms of approaches etc, even if you have to adapt to the idiosyncracies.

If you have no coding experience, then R is likely easier.

If you do have experience in other languages, then it is likely you will constantly be wondering/questioning why R implements things the way they do.

2 Likes

I’ve all but weaned myself off of ArcGIS :raised_hands:. I use QGIS but mostly just for visualisation these days. QGIS’ map composer is improving all the time. I do most heavy geoprocessing in R via raster and sf etc. Rsagacmd is an excellent bridge to SAGA-GIS for terrain analysis.

1 Like

Yes.

I do love the whole R ecosystem for its convenience, but it’s not my favourite language to program in.

As with all things, it depends on your background and what you want to achieve. I’m a retired research ecologist and former programmer who’s dabbled in GIS now and then. Started out with GRASS because I was working with satellite raster data and that’s what GRASS was invented for, before it became as fully-featured as ArcInfo. I was also an early adopter of S and switched to R when it came out, and used to consider myself a “do everything in R” type of person. I’ve done some GIS work in R, mostly for displaying results of spatial simulations and spatial statistics analysis, but now that I’m retired and doing mostly my own natural history workplay, all I need to do is add points and lines to a basemap, and am finding either QGIS or Python much easier for that. So, my recommendations:

  • If you’re not a coder and just want to put together nice-looking maps, QGIS is good.
  • If you are a coder, then R or Python (or Ruby if you want to be a complete iNaturalist junky) are both useful. Both support interfaces to lots of mapping options.
  • R is better if your emphasis is on statistics and you just need a bit of GIS.
  • Python is better for organizing and maintaining a program (as mentioned
    in previous comments).

@andy71 mentioned leaflet.js, which I had just discovered - a handy tool for quick web maps. As an example, it took me only 4 hours to learn the basics of leaflet and hack a script in Python (using ‘folium’, the Python leaflet interface) to display a GPX track in a web map. (Of course, there are faster ways to do that, but now I did it my way :grinning:.)

4 Likes

since a couple of you guys mentioned Leaflet, i started to experiment with a bit. it is indeed relatively easy to use, and the results are decent. i created the bones of a web map that contains references to a lot of miscellaneous XYZ map tile sets and also can show iNaturalist observation pins (with pop-up details), observation heatmaps, place polygons, and taxon ranges / places.

if anyone’s interested, here’s the html code i’ve written so far (not super clean, but useable). you can copy and paste it into a text document (or html editor), tweak as you like, and save it as an .html file. then you’ll be able to open it in any web browser.

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

<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="description" content="Map" />
<title>Map</title>

<style>
   body { height:100vh; margin:0px; }
   #mapid { height:100%; background:yellow; }
</style>
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.5.1/dist/leaflet.css" integrity="sha512-xwE/Az9zrjBIphAcBb3F6JVqxf46+CDLwfLMHloNu6KEQCAWi6HcDUbeOfBIptF7tcCzusKFjFw2yuvEpDL9wQ==" crossorigin=""/>
<script src="https://unpkg.com/leaflet@1.5.1/dist/leaflet.js" integrity="sha512-GffPMF3RvMeYyc1LWMHtK8EbPv0iNZ8/oTtHPx9/cc2ILxQ+u905qIwdpULaqDkyBKgOaB57QTMg7ztg8Jm2Og==" crossorigin=""></script>

<script>
/*
https://github.com/mapbox/corslite
BSD 2-Clause License

Copyright (c) 2017, Mapbox
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

* Redistributions of source code must retain the above copyright notice, this
  list of conditions and the following disclaimer.

* Redistributions in binary form must reproduce the above copyright notice,
  this list of conditions and the following disclaimer in the documentation
  and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

function corslite(url, callback, cors) {
    var sent = false;

    if (typeof window.XMLHttpRequest === 'undefined') {
        return callback(Error('Browser not supported'));
    }

    if (typeof cors === 'undefined') {
        var m = url.match(/^\s*https?:\/\/[^\/]*/);
        cors = m && (m[0] !== location.protocol + '//' + location.hostname +
                (location.port ? ':' + location.port : ''));
    }

    var x = new window.XMLHttpRequest();

    function isSuccessful(status) {
        return status >= 200 && status < 300 || status === 304;
    }

    if (cors && !('withCredentials' in x)) {
        // IE8-9
        x = new window.XDomainRequest();

        // Ensure callback is never called synchronously, i.e., before
        // x.send() returns (this has been observed in the wild).
        // See https://github.com/mapbox/mapbox.js/issues/472
        var original = callback;
        callback = function() {
            if (sent) {
                original.apply(this, arguments);
            } else {
                var that = this, args = arguments;
                setTimeout(function() {
                    original.apply(that, args);
                }, 0);
            }
        }
    }

    function loaded() {
        if (
            // XDomainRequest
            x.status === undefined ||
            // modern browsers
            isSuccessful(x.status)) callback.call(x, null, x);
        else callback.call(x, x, null);
    }

    // Both `onreadystatechange` and `onload` can fire. `onreadystatechange`
    // has [been supported for longer](http://stackoverflow.com/a/9181508/229001).
    if ('onload' in x) {
        x.onload = loaded;
    } else {
        x.onreadystatechange = function readystate() {
            if (x.readyState === 4) {
                loaded();
            }
        };
    }

    // Call the callback with the XMLHttpRequest object as an error and prevent
    // it from ever being called again by reassigning it to `noop`
    x.onerror = function error(evt) {
        // XDomainRequest provides no evt parameter
        callback.call(this, evt || true, null);
        callback = function() { };
    };

    // IE9 must have onprogress be set to a unique function.
    x.onprogress = function() { };

    x.ontimeout = function(evt) {
        callback.call(this, evt, null);
        callback = function() { };
    };

    x.onabort = function(evt) {
        callback.call(this, evt, null);
        callback = function() { };
    };

    // GET is the only supported HTTP Verb by XDomainRequest and is the
    // only one supported here.
    x.open('GET', url, true);

    // Send the request. Sending data is not supported.
    x.send(null);
    sent = true;

    return x;
}

if (typeof module !== 'undefined') module.exports = corslite;
</script>

<script>
/*
https://github.com/consbio/Leaflet.UTFGrid/blob/master/L.UTFGrid.js
Copyright (c) 2015 - 2017, Conservation Biology Institute

Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.

THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/

//heavily modified from: https://raw.githubusercontent.com/danzel/Leaflet.utfgrid/leaflet-master/src/leaflet.utfgrid.js
//depends on corslite

L.UTFGrid = L.TileLayer.extend({
	options: {
		resolution: 4,
		pointerCursor: true,
        mouseInterval: 66  // Delay for mousemove events
	},

	_mouseOn: null,
    _mouseOnTile: null,
    _tileCharCode: null, // '<tileKey>:<charCode>' or null
    _cache: null, // {<tileKey>: <utfgrid>}
    _idIndex: null, // {<featureID>: {<tileKey1>: true, ...<tileKeyN>: true} }
    _throttleMove: null, // holds throttled mousemove handler
    //_throttleConnectEventHandlers: null, // holds throttled connection setup function

    _updateCursor: function(){ }, //no-op, overridden below

	onAdd: function (map) {
        this._cache = {};
        this._idIndex = {};

        L.TileLayer.prototype.onAdd.call(this, map);

        this._throttleMove = L.Util.throttle(this._move, this.options.mouseInterval, this);

        if (this.options.pointerCursor) {
            this._updateCursor = function(cursor) { this._container.style.cursor = cursor; }
        }

        map.on('boxzoomstart', this._disconnectMapEventHandlers, this);
        // have to throttle or we get an immediate click event on boxzoomend
        map.on('boxzoomend', this._throttleConnectEventHandlers, this);
        this._connectMapEventHandlers();
	},

	onRemove: function () {
		var map = this._map;
        map.off('boxzoomstart', this._disconnectMapEventHandlers, this);
        map.off('boxzoomend', this._throttleConnectEventHandlers, this);
        this._disconnectMapEventHandlers();
		this._updateCursor('');
        L.TileLayer.prototype.onRemove.call(this, map);
	},

    createTile: function(coords) {
        this._loadTile(coords);
        return document.createElement('div');  // empty DOM node, required because this overrides L.TileLayer
	},

    setUrl: function(url, noRedraw) {
        this._cache = {};
        return L.TileLayer.prototype.setUrl.call(this, url, noRedraw);
    },

    _connectMapEventHandlers: function(){
        this._map.on('click', this._onClick, this);
        this._map.on('mousemove', this._throttleMove, this);
    },

    _disconnectMapEventHandlers: function(){
        this._map.off('click', this._onClick, this);
		this._map.off('mousemove', this._throttleMove, this);
    },

    _throttleConnectEventHandlers: function() {
        setTimeout(this._connectMapEventHandlers.bind(this), 100);
    },

    _update: function (center, zoom) {
        L.TileLayer.prototype._update.call(this, center, zoom);
    },

    _loadTile: function (coords) {
        var url = this.getTileUrl(coords);
		var key = this._tileCoordsToKey(coords);
		var self = this;
        if (this._cache[key]) { return }
        corslite(url, function(err, response){
            if (err) {
                self.fire('error', {error: err});
                return;
            }
            var data = JSON.parse(response.responseText);
            self._cache[key] = data;
            L.Util.bind(self._handleTileLoad, self)(key, data);
        }, true);
	},

    _handleTileLoad: function(key, data) {
        // extension point
    },

	_onClick: function (e) {
		this.fire('click', this._objectForEvent(e));
	},

	_move: function (e) {
        if (e.latlng == null){ return }

		var on = this._objectForEvent(e);

        if (on._tileCharCode !== this._tileCharCode) {
			if (this._mouseOn) {
				this.fire('mouseout', {
                    latlng: e.latlng,
                    data: this._mouseOn,
                    _tile: this._mouseOnTile,
                    _tileCharCode: this._tileCharCode
                });
				this._updateCursor('');
			}
			if (on.data) {
				this.fire('mouseover', on);
				this._updateCursor('pointer');
			}

			this._mouseOn = on.data;
            this._mouseOnTile = on._tile;
            this._tileCharCode = on._tileCharCode;
		} else if (on.data) {
			this.fire('mousemove', on);
		}
	},

	_objectForEvent: function (e) {
	    if (!e.latlng) return;  // keyboard <ENTER> events also pass through as click events but don't have latlng

        var map = this._map,
		    point = map.project(e.latlng),
		    tileSize = this.options.tileSize,
		    resolution = this.options.resolution,
		    x = Math.floor(point.x / tileSize),
		    y = Math.floor(point.y / tileSize),
		    gridX = Math.floor((point.x - (x * tileSize)) / resolution),
		    gridY = Math.floor((point.y - (y * tileSize)) / resolution),
			max = map.options.crs.scale(map.getZoom()) / tileSize;

        x = (x + max) % max;
        y = (y + max) % max;

        var tileKey = this._tileCoordsToKey({z: map.getZoom(), x: x, y: y});

		var data = this._cache[tileKey];
		if (!data) {
			return {
                latlng: e.latlng,
                data: null,
                _tile: null,
                _tileCharCode: null
            };
		}

        var charCode = data.grid[gridY].charCodeAt(gridX);
		var idx = this._utfDecode(charCode),
		    key = data.keys[idx],
		    result = data.data[key];

		if (!data.data.hasOwnProperty(key)) {
			result = null;
		}

		return {
            latlng: e.latlng,
            data: result,
            id: (result)? result.id: null,
            _tile: tileKey,
            _tileCharCode: tileKey + ':' + charCode
        };
	},

    _dataForCharCode: function (tileKey, charCode) {
        var data = this._cache[tileKey];
        var idx = this._utfDecode(charCode),
		    key = data.keys[idx],
		    result = data.data[key];

		if (!data.data.hasOwnProperty(key)) {
			result = null;
		}
        return result;
    },

	_utfDecode: function (c) {
		if (c >= 93) {
			c--;
		}
		if (c >= 35) {
			c--;
		}
		return c - 32;
	},

    _utfEncode: function (c) {
        //reverse of above, returns charCode for c
        //derived from: https://github.com/mapbox/glower/blob/mb-pages/src/glower.js#L37
        var charCode = c + 32;
        if (charCode >= 34) {
            charCode ++;
        }
        if (charCode >= 92) {
            charCode ++;
        }
        return charCode;
    }
});

L.utfGrid = function (url, options) {
	return new L.UTFGrid(url, options);
};
</script>

</head>

<body>
<div id="mapid"></div>
<script>

// Stamen layers
var s_stamen_copyright = 'Map tiles by <a href="https://stamen.com">Stamen Design</a>, under <a href="https://creativecommons.org/licenses/by/3.0">CC BY 3.0</a>. Data by <a href="https://openstreetmap.org">OpenStreetMap</a>, under <a href="https://www.openstreetmap.org/copyright">ODbL</a>.'; // used for all sets except Watercolor
var s_stamen_urlbase = 'https://stamen-tiles-{s}.a.ssl.fastly.net/';
var l_stamen_watercolor = L.tileLayer(s_stamen_urlbase+'watercolor/{z}/{x}/{y}.jpg',{maxZoom:20, attribution:'Map tiles by <a href="http://stamen.com">Stamen Design</a>, under <a href="https://creativecommons.org/licenses/by/3.0">CC BY 3.0</a>. Data by <a href="https://openstreetmap.org">OpenStreetMap</a>, under <a href="https://creativecommons.org/licenses/by-sa/3.0">CC BY SA</a>.'});
var l_stamen_terrain = L.tileLayer(s_stamen_urlbase+'terrain/{z}/{x}/{y}.jpg',{maxNativeZoom:16, attribution:s_stamen_copyright});
var l_stamen_terrainbg = L.tileLayer(s_stamen_urlbase+'terrain-background/{z}/{x}/{y}.jpg',{maxNativeZoom:16, attribution:s_stamen_copyright});
var l_stamen_terrainlines = L.tileLayer(s_stamen_urlbase+'terrain-lines/{z}/{x}/{y}.jpg',{maxNativeZoom:16, attribution:s_stamen_copyright});
var l_stamen_terrainlabels = L.tileLayer(s_stamen_urlbase+'terrain-labels/{z}/{x}/{y}.jpg',{maxNativeZoom:16, attribution:s_stamen_copyright});
var l_stamen_toner = L.tileLayer(s_stamen_urlbase+'toner/{z}/{x}/{y}.png',{maxZoom:20, attribution:s_stamen_copyright});
var l_stamen_tonerlite = L.tileLayer(s_stamen_urlbase+'toner-lite/{z}/{x}/{y}.png',{maxZoom:20, attribution:s_stamen_copyright});
var l_stamen_tonerbg = L.tileLayer(s_stamen_urlbase+'toner-background/{z}/{x}/{y}.png',{maxZoom:20, attribution:s_stamen_copyright});
var l_stamen_tonerhybrid = L.tileLayer(s_stamen_urlbase+'toner-hybrid/{z}/{x}/{y}.png',{maxZoom:20, attribution:s_stamen_copyright});
var l_stamen_tonerlines = L.tileLayer(s_stamen_urlbase+'toner-lines/{z}/{x}/{y}.png',{maxZoom:20, attribution:s_stamen_copyright});
var l_stamen_tonerlabels = L.tileLayer(s_stamen_urlbase+'toner-labels/{z}/{x}/{y}.png',{maxZoom:20, attribution:s_stamen_copyright});

// Carto layers (also designed by Stamen for Carto)
var s_carto_copyright = 'Map tiles by <a href="https://carto.com">Carto</a>, under <a href="https://creativecommons.org/licenses/by/3.0">CC BY 3.0</a>. Data by <a href="https://openstreetmap.org">OpenStreetMap</a>, under ODbL.';
var s_carto_urlbase = 'https://cartodb-basemaps-{s}.global.ssl.fastly.net/'
var l_carto_light = L.tileLayer(s_carto_urlbase+'light_all/{z}/{x}/{y}.png',{maxZoom:20, attribution:s_carto_copyright});
var l_carto_lightbg = L.tileLayer(s_carto_urlbase+'light_nolabels/{z}/{x}/{y}.png',{maxZoom:20, attribution:s_carto_copyright});
var l_carto_lightlabels = L.tileLayer(s_carto_urlbase+'light_only_labels/{z}/{x}/{y}.png',{maxZoom:20, attribution:s_carto_copyright});
var l_carto_dark = L.tileLayer(s_carto_urlbase+'dark_all/{z}/{x}/{y}.png',{maxZoom:20, attribution:s_carto_copyright});
var l_carto_darkbg = L.tileLayer(s_carto_urlbase+'dark_nolabels/{z}/{x}/{y}.png',{maxZoom:20, attribution:s_carto_copyright});
var l_carto_darklabels = L.tileLayer(s_carto_urlbase+'dark_only_labels/{z}/{x}/{y}.png',{maxZoom:20, attribution:s_carto_copyright});
var l_carto_voyager = L.tileLayer(s_carto_urlbase+'rastertiles/voyager/{z}/{x}/{y}.png',{maxZoom:20, attribution:s_carto_copyright});
var l_carto_voyagerbg = L.tileLayer(s_carto_urlbase+'rastertiles/voyager_nolabels/{z}/{x}/{y}.png',{maxZoom:20, attribution:s_carto_copyright});
var l_carto_voyagerlabels = L.tileLayer(s_carto_urlbase+'rastertiles/voyager_only_labels/{z}/{x}/{y}.png',{maxZoom:20, attribution:s_carto_copyright});
var l_carto_voyagerunder = L.tileLayer(s_carto_urlbase+'rastertiles/voyager_labels_under/{z}/{x}/{y}.png',{maxZoom:20, attribution:s_carto_copyright});

// USGS maps (US only)
var s_usgs_urlbase = 'https://basemap.nationalmap.gov/arcgis/rest/services/'
var l_usgs_topo = L.tileLayer(s_usgs_urlbase+'USGSTopo/MapServer/tile/{z}/{y}/{x}',{maxNativeZoom:16, attribution:'<a href="'+s_usgs_urlbase+'USGSTopo/MapServer">USGS The National Map</a>: National Boundaries Dataset, 3DEP Elevation Program, Geographic Names Information System, National Hydrography Dataset, National Land Cover Database, National Structures Dataset, and National Transportation Dataset; USGS Global Ecosystems; U.S. Census Bureau TIGER/Line data; USFS Road Data; Natural Earth Data; U.S. Department of State Humanitarian Information Unit; and NOAA National Centers for Environmental Information, U.S. Coastal Relief Model'});
var l_usgs_img = L.tileLayer(s_usgs_urlbase+'USGSImageryOnly/MapServer/tile/{z}/{y}/{x}',{maxNativeZoom:16, attribution:'<a href="'+s_usgs_urlbase+'USGSImageryOnly/MapServer">USGS The National Map</a>: Orthoimagery'});
var l_usgs_imgtopo = L.tileLayer(s_usgs_urlbase+'USGSImageryTopo/MapServer/tile/{z}/{y}/{x}',{maxNativeZoom:16, attribution:'<a href="'+s_usgs_urlbase+'USGSImageryTopo/MapServer">USGS The National Map</a>: Orthoimagery and US Topo'});
var l_usgs_relief = L.tileLayer(s_usgs_urlbase+'USGSShadedReliefOnly/MapServer/tile/{z}/{y}/{x}',{maxNativeZoom:16, attribution:'<a href="'+s_usgs_urlbase+'USGSShadedReliefOnly/MapServer">USGS The National Map</a>: 3D Elevation Program'});
var l_usgs_hydro = L.tileLayer(s_usgs_urlbase+'USGSHydroCached/MapServer/tile/{z}/{y}/{x}',{maxNativeZoom:16, attribution:'<a href="'+s_usgs_urlbase+'USGSHydroCached/MapServer">USGS The National Map</a>: National Hydrography Dataset'});

// OpenStreetMaps & OpenTopoMap
var l_osm_fr = L.tileLayer('https://{s}.tile.openstreetmap.fr/osmfr/{z}/{x}/{y}.png', {maxZoom:20, attribution:'donn&eacute;es &copy; <a href="https://osm.org/copyright">OpenStreetMap</a>/ODbL - rendu <a href="https://openstreetmap.fr">OSM France</a>'});
var l_osm_hot = L.tileLayer('https://{s}.tile.openstreetmap.fr/hot/{z}/{x}/{y}.png', {maxZoom:19, attribution:'donn&eacute;es &copy; <a href="https://osm.org/copyright">OpenStreetMap</a>/ODbL - Tiles courtesy of <a href="https://hot.openstreetmap.org/">Humanitarian OpenStreetMap Team</a>'});
var l_otm = L.tileLayer('https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png',{maxNativeZoom:17, attribution:'Kartendaten: &copy; <a href="https://openstreetmap.org/copyright">OpenStreetMap</a>-Mitwirkende, SRTM | Kartendarstellung: &copy; <a href="http://opentopomap.org/">OpenTopoMap</a> (<a href="https://creativecommons.org/licenses/by-sa/3.0/">CC-BY-SA</a>)'});

// University of Wisconsin Real Earth layers (selected from over 500 sets available)
var s_re_urlbase = 'https://realearth.ssec.wisc.edu/'
var l_re_globalvis = L.tileLayer(s_re_urlbase+'tiles/globalvis/{z}/{x}/{y}.png',{maxZoom:20, attribution:'<a href="'+s_re_urlbase+'products/globalvis">RealEarth</a> by SSEC &#64; University of Wisconsin-Madison'});
var l_re_globalir = L.tileLayer(s_re_urlbase+'tiles/globalir-rr/{z}/{x}/{y}.png',{maxZoom:20, attribution:'<a href="'+s_re_urlbase+'products/globalir-rr">RealEarth</a> by SSEC &#64; University of Wisconsin-Madison'});
var l_re_globalwv = L.tileLayer(s_re_urlbase+'tiles/globalwv/{z}/{x}/{y}.png',{maxZoom:20, attribution:'<a href="'+s_re_urlbase+'products/globalwv">RealEarth</a> by SSEC &#64; University of Wisconsin-Madison'});
var l_re_viirsmask = L.tileLayer(s_re_urlbase+'tiles/VIIRS-MASK-54000x27000/{z}/{x}/{y}.png',{maxZoom:20, attribution:'<a href="'+s_re_urlbase+'products/VIIRS-MASK-54000x27000">RealEarth</a> by SSEC &#64; University of Wisconsin-Madison'});
var l_re_nightlights = L.tileLayer(s_re_urlbase+'tiles/NightLightsColored/{z}/{x}/{y}.png',{maxZoom:20, attribution:'<a href="'+s_re_urlbase+'products/NightLightsColored">RealEarth</a> by SSEC &#64; University of Wisconsin-Madison'});

// Wikipedia
//var l_wiki_hillshading = L.tileLayer('http://c.tiles.wmflabs.org/hillshading/{z}/{x}/{y}.png',{maxNativeZoom:14, attribution:'NASA'});
//https://tiles.wmflabs.org/bw-mapnik/${z}/{x}/{y}.png
//https://tiles.wmflabs.org/osm-no-labels/{z}/{x}/{y}.png

// NASA World Wind
// https://worldwind.arc.nasa.gov/

// ==================
// iNaturalist Layers
// ==================
var s_inat_url = 'https://api.inaturalist.org/v1/'

// Build query string for iNat observation layers
function fbuildqs(ary) {
   var qs = '';
   for (i=0;i<ary.length;i++) {
      qs += (i==0 ? '?' : '&')+ary[i];
   };
   return qs;
};
var inat_query_string = fbuildqs([
//   'color=magenta', //set the color of the pins, circles, and heatmap
//   'id=26357908,26454874', //observation id; separate values with comma
   'user_id=pisum', //separate values with comma
//   'place_id=1', //separate values with comma
//   'project_id=2', //separate values with comma
//   'taxon_id=3,630955', //separate values with comma
//   'd1=2019/04/01', // date range from
//   'd2=2019/04/30', // date range to
//   'year=2019', //separate values with comma
//   'month=1,2', //separate values with comma
//   'day=1', // separate values with comma
//   'verifiable=true',
//   'quality_grade=research', //research,needs_id,casual
]);

// Create iNat observation image tileset layers
var l_inat_pts = L.tileLayer(s_inat_url+'points/{z}/{x}/{y}.png'+inat_query_string,{maxZoom:20, attribution:'<a href="'+s_inat_url+'docs/#!/Observation_Tiles/get_points_zoom_x_y_png">iNaturalist observation data</a>'});
var l_inat_cheat = L.tileLayer(s_inat_url+'colored_heatmap/{z}/{x}/{y}.png'+inat_query_string,{maxZoom:20, attribution:'<a href="'+s_inat_url+'docs/#!/Observation_Tiles/get_colored_heatmap_zoom_x_y_png">iNaturalist observation data</a>'});
var l_inat_heat = L.tileLayer(s_inat_url+'heatmap/{z}/{x}/{y}.png'+inat_query_string,{maxZoom:20, attribution:'<a href="'+s_inat_url+'docs/#!/Observation_Tiles/get_heatmap_zoom_x_y_png">iNaturalist observation data</a>'});

// Create iNaturalist UTFGrids and pair each with an observation tileset from above (skip heatmap, since there aren't distinct points in that)
function fpopup(obs) {
   var s = (obs.photos.length==0) ? '[No Photo]' : '<img src="'+obs.photos[0].url.replace('square','small')+'" />';
   s += (obs.photos.length>1) ? ' ['+obs.photos.length+']' : '';
   s += '<br />observation #: <a href="'+obs.uri+'">'+obs.id+'</a> (grade: '+obs.quality_grade+')';
   s += '<br />taxon: ' + ((obs.taxon===null) ? '[Unknown]' : (obs.taxon.preferred_common_name+' ('+obs.taxon.name+')'));
   s += '<br />observer: '+obs.user.login
      + '<br />location: '+obs.place_guess
      + '<br />coordinates: '+Math.round(obs.geojson.coordinates[1]*1000000)/1000000+', '+Math.round(obs.geojson.coordinates[0]*1000000)/1000000;
   s += (obs.positional_accuracy===null) ? '' : ' ('+Math.round(obs.positional_accuracy*10)/10+'m)';
   s += '<br />observed: '+((obs.time_observed_at===null) ? ((obs.observed_on===null) ? '[Unknown]': obs.observed_on) : obs.time_observed_at.replace('T',' '));
   s += '<br />created: '+((obs.created_at===null) ? obs.created_at_details.date : obs.created_at.replace('T',' '));
   s += '<br />last updated: '+obs.updated_at.replace('T',' ');
   if (obs.description===null) {}
   else if (obs.description.length < 200) {s += '<br />'+obs.description }
   else {s += '<br />'+(obs.description.substring(0,200)+'... (more)')};
   L.popup().setLatLng([obs.geojson.coordinates[1],obs.geojson.coordinates[0]])
      .setContent(s).openOn(mymap);
};

var u_inat_pts = L.utfGrid(s_inat_url+'points/{z}/{x}/{y}.grid.json'+inat_query_string,{maxZoom:20});//no attribution here because it won't ever exist on its own
u_inat_pts.on("click", function(e) { // "mouseover" and "mouseout" events not used here
   if (e.data) {
      corslite(s_inat_url+'observations/'+e.data.id, function(err, response) {
         if (err) {
            self.fire('error', {error: err});
            return;
         };
         var obsdata = JSON.parse(response.responseText);
         fpopup(obsdata.results[0]);
       }, true);
   };
});
var g_inat_pts = L.layerGroup([l_inat_pts, u_inat_pts]); // pair the UTFGrid with its map tile set.

var u_inat_cheat = L.utfGrid(s_inat_url+'colored_heatmap/{z}/{x}/{y}.grid.json'+inat_query_string,{maxZoom:20});//no attribution here because it won't ever exist on its own
u_inat_cheat.on("click", function(e) { // "mouseover" and "mouseout" events not used here
   if (e.data) {
      corslite(s_inat_url+'observations/'+e.data.id, function(err, response) {
         if (err) {
            self.fire('error', {error: err});
            return;
         };
         var obsdata = JSON.parse(response.responseText);
         fpopup(obsdata.results[0]);
       }, true);
   };
});
var g_inat_cheat = L.layerGroup([l_inat_cheat, u_inat_cheat]);

// iNaturalist Place Layer
var inat_place_id = '83006'; //integer 
var l_inat_place = L.tileLayer(s_inat_url+'places/'+inat_place_id+'/{z}/{x}/{y}.png',{maxZoom:20, attribution:'<a href="'+s_inat_url+'docs/#!/Polygon_Tiles/get_places_place_id_zoom_x_y_png">iNaturalist place polygon</a>'});

// iNaturalist Taxon Places Checklist and Range Layers
var inat_taxon_id = '8229'; //integer 
var l_inat_taxonplace = L.tileLayer(s_inat_url+'taxon_places/'+inat_taxon_id+'/{z}/{x}/{y}.png',{maxZoom:20, attribution:'<a href="'+s_inat_url+'docs/#!/Polygon_Tiles/get_taxon_places_taxon_id_zoom_x_y_png">iNaturalist taxon place checklist data</a>'});
var l_inat_taxonrange = L.tileLayer(s_inat_url+'taxon_ranges/'+inat_taxon_id+'/{z}/{x}/{y}.png',{maxZoom:20, attribution:'<a href="'+s_inat_url+'docs/#!/Polygon_Tiles/get_taxon_ranges_taxon_id_zoom_x_y_png">iNaturalist taxon range data</a>'});

// create map, and set default center coordinates, zoom level, and layers
var mymap = L.map('mapid', {
   center: [29.75, -95.36],
   zoom: 10,
   layers: [l_usgs_relief, l_usgs_hydro, l_stamen_tonerhybrid, g_inat_pts, l_inat_place],
   doubleClickZoom: false
});

// define available basemaps (can view only one at a time)
var basemaps = {
   "Carto Light": l_carto_light,
   "Carto Light Background": l_carto_lightbg,
   "Carto Dark": l_carto_dark,
   "Carto Dark Background": l_carto_darkbg,
   "Carto Voyager": l_carto_voyager,
   "Carto Voyager Background": l_carto_voyagerbg,
   "Carto Voyager Labels Under": l_carto_voyagerunder,
   "Stamen Watercolor": l_stamen_watercolor,
   "Stamen Terrain Terrain": l_stamen_terrain,
   "Stamen Terrain Background": l_stamen_terrainbg,
   "Stamen Toner": l_stamen_toner,
   "Stamen Toner Background": l_stamen_tonerbg,
   "Stamen Toner Lite": l_stamen_tonerlite,
   "OpenTopoMap": l_otm,
   "OpenStreetMap France": l_osm_fr,
   "OpenStreetMap Humanitarian": l_osm_hot,
   "USGS Imagery": l_usgs_img,
   "USGS Topo": l_usgs_topo,
   "USGS Imagery + Topo": l_usgs_imgtopo,
   "USGS Relief": l_usgs_relief,
   "RealEarth Global Visible": l_re_globalvis,
   "RealEarth Global Water Vapor": l_re_globalwv,
   "RealEarth Global Black Marble": l_re_viirsmask
};

// define available overlay maps (can view more than one at a time, arranged in order from lowest to highest)
var overlaymaps = {
   "USGS Hydro": l_usgs_hydro,
   "RealEarth Global Night Lights": l_re_nightlights,
   "RealEarth Global Rain Rate": l_re_globalir,
   "Stamen Terrain Lines": l_stamen_terrainlines,
   "Stamen Toner Lines": l_stamen_tonerlines,
   "Stamen Toner Hybrid": l_stamen_tonerhybrid,
   "Carto Light Labels": l_carto_lightlabels,
   "Carto Dark Labels": l_carto_darklabels,
   "Carto Voyager Labels": l_carto_voyagerlabels,
   "Stamen Terrain Labels": l_stamen_terrainlabels,
   "Stamen Toner Labels": l_stamen_tonerlabels,
   "iNaturalist Taxon Range": l_inat_taxonrange,
   "iNaturalist Taxon Places": l_inat_taxonplace,
   "iNaturalist Place": l_inat_place,
   "iNaturalist Heatmap": l_inat_heat,
   "iNaturalist Circles": g_inat_cheat,
   "iNaturalist Points": g_inat_pts
};

// add a layer selector control and scale bar
L.control.layers(basemaps, overlaymaps).addTo(mymap);
L.control.scale().addTo(mymap);

// add an indicator for zoom level
// todo: show mouseover coordinates
/*
var ZoomViewer = L.Control.extend({
   onAdd: function(){
      var gauge = L.DomUtil.create('div');
      gauge.style.width = '100px';
      gauge.style.background = 'rgba(255,255,255,0.5)';
      gauge.style.textAlign = 'left';
      gauge.style.padding = '0px 5px';
      mymap.on('zoomstart zoom zoomend', function(ev) { gauge.innerHTML = 'Zoom level: ' + Math.round(mymap.getZoom());
      })
      return gauge;
   }
});
(new ZoomViewer).addTo(mymap);
*/
</script>
</body>

</html>

UPDATE: here’s a screenshot of what the code above produces:

1 Like

I did a lot of productivity and analysis scripting in ArcView 3.x back in the day, and have managed to keep it hobbling along under Windows 7. Actually it runs great and continues to be hugely productive for me. Just have to not interrupt its startup or project loading processes by switching to any other windows. After that it plays nicely with everything else.

Soon I have the daunting prospect of discovering whether or not I can further migrate it to Windows 10. And of course if the shapefile format ever stops being supported…

1 Like

thanks for these resources! I use Q but have increasingly been bending my neophyte R skills toward geospatial applications. These references should come in handy.

1 Like

This topic was automatically closed 60 days after the last reply. New replies are no longer allowed.