Smathermather's Weblog

Remote Sensing, GIS, Ecology, and Oddball Techniques

Posts Tagged ‘Leaflet’

Leaflet Crosshairs

Posted by smathermather on February 18, 2016

Ok, I’m not the first to come up with that name, but I like it so it’s staying for now.

The problem space is this: Your web development team is good with forms. They build forms like diurnal animals wake — daily: day in, day out, day in, day out. They want to build a form for a mobile web app and it just happens to use HTML5 to grab the geolocation from field deployed phones, tablets, etc.. Great! Geo problem solved — when we collect our form data, we’ll get geo-data for free.

Not so fast, form-building Valentine — what’s the quality of those data? How accurate is that phone GPS? Is it good enough? The answer is probably yes, most of the time.

But, good enough most of the time isn’t good enough, Faye. What I want is an embedded map where I can move the crosshairs to the actual location inside the form. It’s like all the corrections you do back at the office, but you can do them in the field while the site is still fresh in your mind. Simple. Useful. (Also, infrastructure like Fulcrum App does it because it’s so simple and useful). Hence, questions like this: http://gis.stackexchange.com/questions/90225/how-to-add-a-floating-crosshairs-icon-above-leaflet-map and pages like this: https://www.mapbox.com/blog/help-search-MH370/

I couldn’t get the solution on Stack Exchange to work for me. Besides, I think its the wrong solution. I don’t want to move the icon back to center on the map moving, I want the map to move and the icon to stay stationary. It’s a fine (and probably irrelevant) distinction, but it feels important to me.

So, we build a small page that has the following features:

  • A crosshairs that is stationary
  • A map that moves
  • When the map moves, our lat/lon values update in our form

Main code as follows (careful — careless use of jquery follows):

<!DOCTYPE html>
<html>
<head>
	<title>Leaflet Crosshairs</title>

	<!-- Include meta tag to ensure proper rendering and touch zooming -->
	<!--<meta name="viewport" content="width=device-width, initial-scale=1" />-->
	<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">

	<!-- Include the jQuery library -->
	<script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
	<!-- Include jQuery Mobile stylesheets -->
	<link rel="stylesheet" href="//ajax.googleapis.com/ajax/libs/jquerymobile/1.4.5/jquery.mobile.min.css">
	<!-- Include the jQuery Mobile library -->
	<script src="//ajax.googleapis.com/ajax/libs/jquerymobile/1.4.5/jquery.mobile.min.js"></script>

	<!-- Include leaflet css and js -->
	<link rel="stylesheet" href="//cdn.leafletjs.com/leaflet/v0.7.7/leaflet.css" />
	<script src="//cdn.leafletjs.com/leaflet/v0.7.7/leaflet.js"></script>

	<style>
	body {
		padding: 0;
		margin: 0;
	}
        html, body, #map {
            height: 100%;
            width:100%;
        }

        #metamap {
            width: 100%;
            height: 300px;
        }
        #crosshair {
            position: relative;
            z-index: 10;
            height: 200px;
            vertical-align: middle;
        }
	#crosshair img {
		position: absolute;
		margin: 0;
		top: 50%;
		left: 50%;
		margin-right: -50%;
		transform: translate(-50%, -50%);
	}
	</style>
</head>
<body>

    <div id="metamap">
        <div id="map">
            <div id="crosshair"><img class="crosshair" src=crosshair.png /></div>
        </div>
    </div>
    <br />
    <hr />
    Latitude: <input type="text" id="txtLatitude" />
    <br /><br />
    Longitude: <input type="text" id="txtLongitude" />
    
        <script>
            // Initiate map
            var map = L.map('map');

            // load map
            L.tileLayer('//api.tiles.mapbox.com/v4/{id}/{z}/{x}/{y}.png?access_token=pk.eyJ1IjoiY2xldmVsYW5kLW1ldHJvcGFya3MiLCJhIjoiWHRKaDhuRSJ9.FGqNSOHwiCr2dmTH2JTMAA', {
                maxZoom: 20,
                id: 'mapbox.satellite'
            }).addTo(map);

            // Now a function to populate our form with latitude and longitude values
            function onMapMove(e) {
                // txtLatitude.val(map.getCenter());
                var locale = map.getCenter();
                $('#txtLatitude').val(locale.lat);
                $('#txtLongitude').val(locale.lng);
            }
            
            // Boilerplate...
            function onLocationError(e) {
                alert(e.message);
            }

            // When the map moves we run our function up above
            map.on('move', onMapMove);

            // Boilerplate
            map.on('locationerror', onLocationError);
            
            // When we load the map, we should zoom to our current position using device geolocation
            map.locate({ setView: true, maxZoom: 20 });
        </script>
</body>
</html>

<body>


<div id="metamap">

<div id="map">

<div id="crosshair"><img class="crosshair" src=crosshair.png /></div>

        </div>

    </div>

    

<hr />

    Latitude: <input type="text" id="txtLatitude" />
    

    Longitude: <input type="text" id="txtLongitude" />
    
        <script>
            // Initiate map
            var map = L.map('map');

            // load map
            L.tileLayer('//api.tiles.mapbox.com/v4/{id}/{z}/{x}/{y}.png?access_token=pk.eyJ1IjoiY2xldmVsYW5kLW1ldHJvcGFya3MiLCJhIjoiWHRKaDhuRSJ9.FGqNSOHwiCr2dmTH2JTMAA', {
                maxZoom: 20,
                id: 'mapbox.satellite'
            }).addTo(map);

            // Now a function to populate our form with latitude and longitude values
            function onMapMove(e) {
                // txtLatitude.val(map.getCenter());
                var locale = map.getCenter();
                $('#txtLatitude').val(locale.lat);
                $('#txtLongitude').val(locale.lng);
            }
            
            // Boilerplate...
            function onLocationError(e) {
                alert(e.message);
            }

            // When the map moves we run our function up above
            map.on('move', onMapMove);

            // Boilerplate
            map.on('locationerror', onLocationError);
            
            // When we load the map, we should zoom to our current position using device geolocation
            map.locate({ setView: true, maxZoom: 20 });
        </script>
</body>
</html>

Screen shot of app in action

Things to fix:

  • Alignment of crosshairs so they are properly centered
  • Better looking crosshairs
  • Rounding for those coordinate values
  • Do we need jQuery? Pro’ly not

That was my fun for the day. Shout-out to Tanetta Jordan my brilliant paired programmer for the day. Without her, this would have taken a week… .

Oh ya, and git-repo here: https://github.com/cleveland-metroparks/leaflet-crosshairs

Look there for updates that should include the improvements above.

Posted in Javascript, Leaflet | Tagged: , , , | Leave a Comment »

They’ve gone to plaid. (LeafletJS and CartoDB)

Posted by smathermather on December 18, 2013

Enjoyed setting the errorTileUrl flag in a leafletjs map today.

errorTileUrl: 'https://encrypted-tbn2.gstatic.com/images?q=tbn:ANd9GcStyzXdR5V4U_BF_DOOBAl3eEJGeNw54O40_Gxj5nXRMc-49Ega0A'

Image of map with plaid background

A small homage:

Posted in Leaflet, Other | Tagged: , , | 2 Comments »

Playing with new tools and old standards: GeoJSON, Leaflet, CartoDB across platforms

Posted by smathermather on February 16, 2012

Leaflet, CartoDB, GeoJSON, and cross platform web map deployment. First some introductions:

Leaflet is a modern, lightweight open-source JavaScript library for interactive maps for desktop and mobile web browsers, developed by CloudMade to form the core of its next generation JavaScript API. Weighting just about 21kb of gzipped JS code, it still has all the features you will ever need for you web mapping needs while providing a fast, smooth, pleasant user experience.”

Not a bad description. There’s more maturity and flexibility to OpenLayers, but still much fun to be had with Leaflet. And as both are Open Source and competing in similar markets, they inform each other, and it’s fun to watch the friendly rivalry between the communities developing the libraries.

In an earlier post, I demonstrated the use of Leaflet in conjunction with CartoDB. What’s cartodb? From their web site:

“CartoDB allows you to map data & develop location aware applications quickly and easily. With plans starting from free, take CartoDB for a test drive today!”

CartoDB is a really slick hosted service (cloud) implementation of PostGIS, similar in some respects to fusion tables functionality. Don’t like the cloud it’s hosted on, or the associated prices? Well, it’s open source, so you can put it on your private cloud too, or set up physical linux machine to host it.

Mix these two things with 3 hours of insomnia the other night, and you get this:

Nice cross platform implementation!  These are pure vector deployed maps– served through Leaflet from CartoDB as GeoJSON.  Here shown deployed to a Blackberry, iPhone, Android, and Kindle Fire (also Android), which a buddy and I pulled up as we sat in a bar tonight.  We also took a picture of this with a windows laptop also running the page for demonstrable non-webkit deployment, but this was the better (albeit fuzzy) picture.  Ah, the wonders of Open Standards.  Next time, we’ll see if we can get a Windows Mobile phone in there, along with Mac, Windows, and Linux desktops.

Still, for now GeoServer and OpenLayers are my babies for deploying GeoSpatial data, but there are a few different ways to deploy through Open Standards using Open Source software, and it’s fun to draw a little from the other geospatial tribes.

Posted in CartoDB, Database, Leaflet, PostGIS, PostgreSQL, Recreation, SQL, Trail Curation, Trails | Tagged: , , , , , , | 2 Comments »

Leaflet, GeoServer, and Open Source Software Ramblings

Posted by smathermather on December 6, 2011

<gushing>

I posted this post about an apparent problem in rendering of GeoJSON in Leaflet, and now it’s fixed a week later.  Why gush now, when for a few years developers on the GeoServer board have been fixing bugs I’ve found (often by the next day)?  Well for one, I very rarely find bugs in GeoServer– I just think I do, document everything, send it on, and then find out I’ve made a mistake.  It’s hard to gush about making a mistake and then having the GeoServer community kind enough to show me the errors (and they are kind about it). I should gush anyway, but ego sometimes prevents it. For example, the last time I found a “bug” in GeoServer, one of the developers fixed it so no one else could make the same mistake (the mistake included forgetting to install fonts) and did it while laid up with an injury.

With Leaflet, I guess what’s really nice is the kindness of strangers, so it comes as a surprise.  The GeoServer listserve has come to feel like family.  They take great care of me and I take them for granted.  I don’t know the Leaflet developers from Adam, so it feels quite warm and fuzzy to have my bugs bugs I’ve discovered squashed anyway, all for pride in a codebase that should function right.

The short and long of it: warm and fuzzies all around. Yay, Open Source.

</gushing>

Posted in Database, GeoServer | Tagged: , , , | Leave a Comment »

CartoDB, Leaflet, and a little anti-generalization

Posted by smathermather on November 28, 2011

CartoDB is one of two hosted (read: cloud) PostGIS database implementations.  It has a maps API, an SQL API, and is some fun to use.  The other hosted PostGIS implementation is SpacialDB which has a Restful API, but can also take SQL.  I just got my key for the free version of that, so hopefully I will be reviewing use of that in the future too.

I can’t beat Bill Dollins’ post CartoDB + Leaflet = Easy, and I hadn’t played with Leaflet before, but am often up to my eyeballs in OpenLayers, so I thought I’d build off Bill’s post.  I haven’t, well, not substantially anyway.  But I did start to and found some strange behavior in displaying GeoJSON in Leaflet, under the right conditions anyway.  This is what I got:

Difference between properly displayed GeoJSON, and GeoJSON with missing point.

The green line shows the correct line, the dark gray line shows how it’s being displayed noticeably incorrectly by Leaflet.  I was about to post to the CartoDB website when I dug a little further:

The problem goes away as you zoom in...

The problem goes away as you zoom in…

and also as you zoom out...

and also as you zoom out… leading me to conclude it’s not a CartoDB issue, but a leaflet issue, as the GeoJSON geometry is not re-requested based on zoom level.  But is it a bug?  I suspect not.  I suspect it’s a feature– I suspect leaflet is auto-generalizing the geometry of the GeoJSON by dropping the occasional node.  Since we have so few nodes along the southern boundary, it matters here.  Maybe they should use a Douglas-Peucker or similar algorithm, but that may not be practical as it’s difficult to know a priori what the tolerance value should be (of course, the Leaflet user might know what value to use, however…).  I’ll post to the leaflet forum tomorrow to find out.  In the mean time, my fix is simple.  Instead of a simple load of the CartoDB geometries:

http://supersecret.cartodb.com/api/v1/sql?q=SELECT%20name,%20the_geom%20FROM%20boundary&format=geojson&callback=?

We’ll use the SQL API to complicate the geometry on the fly with ST_Segmentize, which will add extra nodes in anticipation of Leaflet simplifying it:

http://supersecret.cartodb.com/api/v1/sql?q=SELECT%20name,%20ST_Segmentize%28the_geom,%200.0001%29%20AS%20the_geom%20FROM%20boundary&format=geojson&callback=?

Yup.  It’s a hack.  I’ll have to find out if there are any alternative (less hackish) solutions.

BTW, rockin’ the CartoDB, but hate thinking in geographic to do segmentization.  In market fairness, I’ll try something similar with SpacialDB soon.

Posted in CartoDB, Database, Leaflet, PostGIS, PostgreSQL, SQL | Tagged: , , , , , , , | 5 Comments »