Smathermather's Weblog

Remote Sensing, GIS, Ecology, and Oddball Techniques

Using foreign data wrapper to use PostGIS with SQLServer

Posted by smathermather on May 29, 2016

Here was the problem that needed solved last week (we have a few similar problems in upcoming projects, so this was an exciting thing to try): we needed to use PostGIS to access data in a SQLServer database. The SQLServer database backs the web site in question, the underlying content management system, etc., so no– removing SQLServer isn’t really an option at this stage. Obviously PostGIS is a requirement too… .

Before I go further, I used tds_fdw as the foreign data wrapper. The limitations here are as follows: it is a read-only connection, and only works with Sybase and SQLServer, as it uses tabular data stream protocol for communicating between client and server. This is not as generic a solution as we can use. Next time I’ll try ogr_fdw which is more generic as it can connect with other databases and other data types. Another advantage to ogr_fdw is we can use IMPORT FOREIGN SCHEMA. Regina Obe and Leo Hsu warn though to limit this to 150 tables or so for performance reasons.

With the limitations listed above, this is how we built the thang:

DROP SERVER beach_fdw CASCADE;

-- Create the server connection with FOREIGN DATA WRAPPER
CREATE SERVER beach_fdw
FOREIGN DATA WRAPPER tds_fdw
OPTIONS (servername 'name_or_ip', port '1433', database 'non-spatial-database', tds_version '7.1', msg_handler 'notice');

-- We map the postgres user to the user that can read the table we're interested in
CREATE USER MAPPING FOR postgres
SERVER beach_fdw
OPTIONS (username 'user', password 'password');

-- Create the actual foreign table connection
CREATE FOREIGN TABLE beach_closures (
AutoNumber int NOT NULL,
Title varchar NOT NULL,
StartDate timestamp without time zone NOT NULL,
WaterQuality varchar NOT NULL,
Latitude varchar NOT NULL,
Longitude varchar NOT NULL,
BeachStatus varchar NOT NULL,
ClosureColor varchar NOT NULL)
SERVER beach_fdw
OPTIONS (schema_name 'schema_name', table_name 'vw_CMPBeachClosures');

-- Now we create a spatial view using our longitude and latitude
CREATE VIEW v_beach_closures AS
SELECT
AutoNumber, Title, StartDate, WaterQuality, Latitude,
Longitude, BeachStatus, ClosureColor, ST_SetSRID(ST_MakePoint(Longitude::numeric, Latitude::numeric), 4326)	AS geom
FROM beach_closures;

Voila! A nice little PostGIS enabled view of a SQLServer view or table!

Posted in Database, PostGIS, PostgreSQL, SQL | Tagged: , , , , | Leave a Comment »

Efficient delivery of raster data part 4

Posted by smathermather on May 1, 2016

Guest post from my colleague Patrick Lorch who wrote up what we did the other day in order to view a whole bunch of tiled images in a directory in QGIS (I did some mild editing to his posts. Mistakes are mine). The great thing about the approach is that is generalizeable to most tools that use GDAL for their raster API. This is part of a series. You can view this series in reverse with this post:

Building virtual datasets from a bunch of tiffs
What do you do when someone gives you aerial images stored as tiles in different directories representing different zoom levels? The goal is to make them easy to use as baselayers in QGIS. The answer is to reference them in a virtual data set (VRT).

gdalbuildvrt is the ticket
First make lists of tiffs
If the directory structure is something like this:

total 8047
drwxr-xr-x 7 pdl    4096 Apr 29 14:22 ./
drwxr-xr-x 5 pdl    4096 Apr 27 22:10 ../
drwxr-xr-x 2 pdl 1310720 Apr 22 21:37 0/
drwxr-xr-x 2 pdl  393216 Apr 22 22:54 1/
drwxr-xr-x 2 pdl   98304 Apr 28 14:44 2/
drwxr-xr-x 2 pdl   32768 Apr 28 14:44 3/
drwxr-xr-x 2 pdl    8192 Apr 28 14:44 4/

Then you first need a set of list files listing tiffs in each directory.

ls 0\*.tif > list0.txt
ls 1\*.tif > list1.txt
ls 2\*.tif > list2.txt
ls 3\*.tif > list3.txt
ls 4\*.tif > list4.txt

Now make the vrts

gdalbuildvrt -input_file_list list0.txt aerial_2015_0.vrt
gdalbuildvrt -input_file_list list1.txt aerial_2015_1.vrt
gdalbuildvrt -input_file_list list2.txt aerial_2015_2.vrt
gdalbuildvrt -input_file_list list3.txt aerial_2015_3.vrt
gdalbuildvrt -input_file_list list4.txt aerial_2015_4.vrt

Now you can open these in QGIS depending on what zoom level you need.

These VRTs may now be loaded as ordinary rasters in QGIS or whatever you please. In this case, we retiled with multiple resample levels (see this post for more info), so we’ll have to define max/min ranges at which the different image collections are visible.

Thanks for the write up Pat!

Posted in GDAL | Tagged: , , , | Leave a Comment »

(Whichever tiler you use) and efficient delivery of raster data (image pyramid layer) (update2)

Posted by smathermather on April 15, 2016

Subdivision of geographic data is a panacea to problems you didn’t know you had.

Maybe you deal with vector data, so you pre-tile your vector data to ship to the browser to render– you’re makin’ smaller data. Maybe you use cutting edge PostGIS so you apply ST_Subdivide to keep your data smaller than the database page size like Paul Ramsey describes here. Smaller’s better… . Or perhaps you are forever reprojecting your data in strange ways, across problematic boundaries or need to buffer in an optimum coordinate system to avoid distortion. Regardless of the reason, smaller is better.

Maybe you aren’t doing vector work, but this time raster. What’s the equivalent tiling process?  I wrote about this for GeoServer almost 5 (eep!) years ago now (with a slightly more recent follow up) and much of what I wrote still applies:

  • Pre-tile your raw data in modest chunks
  • Use geotiff so you can use internal data structures to have even smaller tiles inside your tiles
  • Create pyramids / pre-summarized data as tiles too.

Fortunately, while these posts were written for GeoServer, they apply to any tiler. Pre-process with gdal_retile.

gdal_retile.py -v -r bilinear -levels 4 -ps 6144 6144 -co "TILED=YES" -co "BLOCKXSIZE=256" -co "BLOCKYSIZE=256" -s_srs EPSG:3734 -targetDir aerial_2011 --optfile list.txt

Let’s break this down a little:

First we choose our resampling method for our pyramids (bilinear). Lanzcos would also be fine here.

-r bilinear

Next we set the number of resampling levels. This will depend on the size of the dataset.

-levels 4

Next we specify the pixel and line size of the output geotiff. This can be pretty large. We probably want to avoid a size that forces the use of bigtiff (i.e. 4GB).

-ps 6144 6144

Now we get into the geotiff data structure — we internally tile the tifs, and make them 256×256 pixels. We could also choose 512. We’re just aiming to have our tile size near to the size that we are going to send to the browser.

-co "TILED=YES" -co "BLOCKXSIZE=256" -co "BLOCKYSIZE=256"

Finally, we specify our coordinate system (this is state plane Ohio), our output directory (needs created ahead of time) and our input file list.

-s_srs EPSG:3734 -targetDir aerial_2011 --optfile list.txt

That’s it. Now you have a highly optimized raster dataset that can:

  • get the level of detail necessary for a given request,
  • and can extract only the data necessary for a given request.
  • Pretty much any geospatial solution which uses GDAL can leverage this work to make for very fast rendering of raster data to a tile cache. If space is an issue, apply compression options that match your use case.

    Posted in GDAL | Tagged: , , , | 2 Comments »

    ~~North Carolina GIS~~ rhymes with Lasagna

    Posted by smathermather on April 12, 2016

    FOSS4GNA 2016 Logo

    Picture of Garfield the cat diving into a dish of lasagna

    Last year I really enjoyed attending and presenting at North Carolina GIS in Raleigh. As many of you know, Free and Open Source Software for Geospatial North America (FOSS4GNA, alleged by some to rhyme with “lasagna”) will be in Raleigh this year, in a short few weeks.

    I highly encourage you to go. First of all, it’s FOSS4GNA, so lots of free and open source geospatial goodness. But, Raleigh and NCGIS in general are a hot spot of open source geospatial stuff. Last years’ North Carolina GIS was a mini FOSS4G, mixed with the standard uhhhh, not so open source crowd. I came expecting (most respectfully) form of (name your favorite state GIS conference). What I saw was that but also more than 20 talks on FOSS Geo stuff. Don’t believe me? Here’s the list:

    mobile_global_open

  • Open-ing the Future of NOAA GIS | Speaker Tony LaVoi
  • The Rise of 3D GIS on the Web | Speaker Patrick Cozzi
  • plas.io and Greyhound: Point Clouds in Your Browser | Speaker Howard Butler
  • Open Source, Open Discussion | Speakers: Ralph Dell GISP, Randal Hale, Jason Hibbets, Dr. Helena Mitasova GISP
  • How to Build Fat Polygons | Speaker Skip Daniels
  • How to Use GitHub to Hire Your Next Analyst | Speakers: Dave Michelson, Cameron Carlyle
  • QGIS for the Desktop | Speaker Randal Hale
  • GRASS7: New Features and Tools for Spatio-Temporal Analytics and Visualization | Speaker Dr. Helena Mitasova
  • MapLoom: A New Web-client With Versioned Editing (GeoGit) Integration | Speakers: Syrus Mesdaghi, Tyler Garner
  • Quality of Life Dashboard | Speaker Tobin Bradley
  • Defaulting to Open (at least trying to…) | Speaker Justin Greco
  • Using Geospatial Applications to Build ForWarn | Speaker Bill Hargrove
  • National Map Corps: Crowdsourcing Map Features for the USGS | Speaker Silvia Terziotti
  • Wake County Open Data: Where Will It Take You? | Speakers: Carter Vickery, Bill Scanlon
  • FOSS and Web Mapping | Speaker Ashley Hanes, A-B Tech CC
  • National Park Service GIS Data + OpenStreetMap = Places of Interest
  • Spatial Analysis of Wildfire Occurrences in North Carolina Using the R Project for Statistical Computing | Speaker David Jones
  • Point Cloud in Your Pocket | Speaker Stephen Mather
  • The Unknowns: An IT Professional’s Guide to Open Source | Speaker Paul Ramsey
  • Open Data? Show Me the Money! | Speaker Blake Esselstyn
  • Exploring Spatial Data Infrastructure in an Open Source World | Speaker Jacqueline Lowe, UNC-A
  • Open Source Geospatial Foundation (OSGeo) | Speaker Doug Newcomb
  • Open Data Kit (ODK) An Exciting, Free, and Open-Source Field Data Collection Alternative | Speaker Eric Wilson
  • But, lest you think this is a new thing, now that FOSS is up and coming in the geo world, in 2013, NCGIS played host to a dozen open source geospatial presentations, and has been on this trend since at least 2001.

    I recommend checking out some of the amazing FOSS Geo work endemic to NC State, while you are there. If you don’t know Helena Mitasova from the Open Source Geospatial Research and Education Laboratory, you should. I’m hoping she and her students have on display their Tangible Landscape which ties a sandbox in to real-time GRASS DEM processing (flow accumulation, viewsheds, fire modeling, etc.):

    http://geospatial.ncsu.edu/osgeorel/tangible-landscape.html

    Screen capture of tangible landscape video.

    Finally, I’d like to really briefly address what is in the minds of many as they think on North Carolina these days — North Carolina’s HB2 legislation. The FOSS4GNA organizers have addressed this bill, and communicated their position and their accommodations. The response, quoted in part below speaks for itself:

    As we shared shortly after HB2 was passed, it was too late to relocate and/or cancel the conference. We are very grateful to the good people who recognized that boycotting FOSS4G NA hurts a very inclusive conference and community. The fact you are coming means a great deal, and we do not take it for granted. Thank you!

    After talking with so many people in the last few weeks, it is very clear that our LGBT attendees, including myself, do not stand alone.

    Here are some of the things we have put in place to help ensure all of our attendees are safe & welcome:

  • There will be 4 gender neutral restrooms. And the venue is updating their signs to clearly state they are gender neutral restrooms.
  • For those interested, we will be encouraging people to make donations to the ACLU, who is suing the State of North Carolina because of HB2.
  • Our code of conduct is in place, and will be enforced by staff.
  • There is a map that lists trans friendly restrooms in the area. Huge thanks to Emily Waggoner for creating it!
  • Our sponsors have taken a stance against HB2.
  • I love this community, and I am so happy to see these responses. Please come and join us. It will be an amazing conference.
    Post script: Thanks to Doug Newcomb for the history lesson on NCGIS. I hope to share more of his info in future posts.

    Posted in FOSS4G-NA | Leave a Comment »

    OpenDroneMap — texturing improvements

    Posted by smathermather on March 27, 2016

    Great news on OpenDroneMap. We now have a branch that has MVS-Texturing integrated, thanks to continuing work by Spotscale, and of course continuing integration work by @dakotabenjamin.

    The MVS-Texturing branch isn’t fully tested yet, nor fully integrated, but the initial results are promising. MVS-Texturing itself handles the problems of choosing the best photos for a given facet on a textured model in order to do a great job texturing a complex scene. This bears the promise of vastly improved textured models and very nice orthophotos.. It seems an ideal drop in for the texturing limitations of OpenDroneMap. From the project site:

    Our method addresses most challenges occurring in such reconstructions: the large number of input images, their drastically varying properties such as image scale, (out-of-focus) blur, exposure variation, and occluders (e.g., moving plants or pedestrians). Using the proposed technique, we are able to texture datasets that are several orders of magnitude larger and far more challenging than shown in related work.

    When we apply this approach to one of our more difficult datasets, which was taken on a partially cloud part of the day…

    IMG_1347_RGB.jpg

    we get very promising results:

     

    Posted in 3D, OpenDroneMap | 1 Comment »

    Taking Slices from LiDAR data: Part VI

    Posted by smathermather on March 19, 2016

    I finally got PDAL properly compiled with Point Cloud Library (PCL) baked in. Word to the wise — CLANG is what the makers are using to compile. The PDAL crew were kind enough to revert the commit which broke GCC support, but why swim upstream? If you are compiling PDAL yourself, use CLANG. (Side note, the revert to support GCC was really helpful for ensuring we could embed PDAL into OpenDroneMap without any compiler changes for that project.)

    With a compiled version of PDAL with the PCL dependencies built in, I can bypass using the docker instance. When I was spawning tens of threads of Docker and then killing them, recovery was a problem (it would often hose my docker install completely). I’m sure there’s some bug to report there, or perhaps spawning 40 docker threads is ill advised for some grander reason, but regardless, running PDAL outside a container has many benefits, including simpler code. If you recall our objectives with this script, we want to:

    • Calculate relative height of LiDAR data
    • Slice that data into bands of heights
    • Load the data into a PostgreSQL/PostGIS/pgPointCloud database.

    The control script without docker becomes as follows:

    #!/bin/bash 
    
    # readlink gets us the full path to the file. This is necessary for docker
    readlinker=`readlink -f $1`
    # returns just the directory name
    pathname=`dirname $readlinker`
    # basename will strip off the directory name and the extension
    name=`basename $1 .las`
    
    # PDAL must be built with PCL.
    # See http://www.pdal.io/tutorial/calculating-normalized-heights.html
    
    pdal translate "$name".las "$name".bpf height --writers.bpf.output_dims="X,Y,Z,Intensity,ReturnNumber,NumberOfReturns,ScanDirectionFlag,EdgeOfFlightLine,Classification,ScanAngleRank,UserData,PointSourceId,HeightAboveGround"
    
    # Now we split the lidar data into slices of heights, from 0-1.5 ft, etc.
    # on up to 200 feet. We're working in the Midwest, so we don't anticipate
    # trees much taller than ~190 feet
    for START in 0:1.5 1.5:3 3:6 6:15 15:30 30:45 45:60 60:105 105:150 150:200
            do
            # We'll use the height classes to name our output files and tablename.
            # A little cleanup is necessary, so we're removing the colon ":".
            nameend=`echo $START | sed s/:/-/g`
    
            # Name our output
            bpfname=$name"_"$nameend.bpf
    
            # Implement the height range filter
            pdal translate $name.bpf $bpfname -f range --filters.range.limits="HeightAboveGround[$START)"
    
            # Now we put our data in the PostgreSQL database.
            pdal pipeline -i pipeline.xml --writers.pgpointcloud.table='pa_layer_'$nameend --readers.bpf.filename=$bpfname --writers.pgpointcloud.overwrite='false'
    done
    

    We still require our pipeline xml in order to set our default options as follows:

    <?xml version="1.0" encoding="utf-8"?>
    <Pipeline version="1.0">
      <Writer type="writers.pgpointcloud">
        <Option name="connection">
          host='localhost' dbname='user' user='user' password=‘password’
        </Option>
        <Option name="table">54001640PAN_heightasz_0-1.5</Option>
        <Option name="compression">dimensional</Option>
        <Filter type="filters.chipper">
          <Option name="capacity">400</Option>
          <Reader type="readers.bpf">
          <Option name="filename">54001640PAN_heightasz_0-1.5.bpf</Option>
          </Reader>
        </Filter>
      </Writer>
    </Pipeline>
    

    And as before, we can use parallel to make this run a little lot faster:

    find . -name '*.las' | parallel -j20 ./pdal_processor.sh
    

    For the record, I found out through testing that my underlying host only has 20 processors (though more cores). No point in running more processes than that… .

    So, when point clouds get loaded, they’re broken up in to “chips” or collections of points. How many chips do we have so far?:

    user=# SELECT COUNT(*) FROM "pa_layer_0-1.5";
      count   
    ----------
     64413535
    (1 row)
    

    Now, how many rows is too many in a PostgreSQL database? Answer:

    In other words, your typical state full of LiDAR (Pennsylvania or Ohio for example) are not too large to store, retrieve, and analyze. If you’re in California or Texas, or have super dense stuff that’s been flown recently, you will have to provide some structure in the form of partitioning your data into separate tables based on e.g. geography. You could also modify your “chipper” size in the XML file. I have used the default 400 points per patch (for about 25,765,414,000 points total), which is fine for my use case as then I do not exceed 100 million rows once the points are chipped:

          <Option name="capacity">400</Option>
    

    Posted in 3D, Database, Docker, LiDAR, Other, PDAL, pointcloud, PostGIS, PostgreSQL | Tagged: , , , , , | Leave a Comment »

    The ultimate kite?

    Posted by smathermather on March 18, 2016

    Loving the new kite we acquired at work. With a year or so of flying under our belts, we’ve learned to love and hate the wind. It seems it is often either too slow or too fast. If it’s too slow, that spells frustration and time lost. If it’s too fast, the same is true, but a fast wind can cause costly equipment damage as well.

    It turns out, there are some really nice kites made in New Zealand for kite fishing that solve the problem of too much wind handily.

    This kite will fish winds of 6 to 50 knots and can be tacked up to 60 degrees off the wind.

    Anecdotally, the kite in question will fly in even faster winds safely, but just to be safe, it’s probably best to fly within specs.

    Another frustrating aspect of kite flying with an objective of collecting data is putting the kite right where you want it, irrespective of wind direction. Fortunately, that’s a solved and well documented problem: https://publiclab.org/notes/mathew/2-1-2013/how-pauls-fishing-kites-flys-wind

    Flies like a dream. Happy flying!

    Posted in Kite Aerial Photography, Other | Tagged: | Leave a Comment »

    OpenDroneMap — Paris Code Sprint

    Posted by smathermather on February 29, 2016

    I failed to make it to the Paris Code Sprint. It just wasn’t in the cards. But, my colleague Dakota and I sprinted anyway, with some help and feedback from the OpenDroneMap community.

    So, what did we do? Dakota did most of the work. He hacked away at the cmake branch of ODM, a branch set up by Edgar Riba to substantially improve the installation process for ODM.

    • Fixed odm_orthophoto in the branch so that it produces geotiffs
    • Fixed PMVS so that it is multithreaded again
    • Added rerun-all and rerun-from function
    • Integrated @lupas78’s additions for an xyz point cloud output
    • Added benchmarking which is an important soft number for when we have code changes
    • (Technically before the sprint) wrote the first test for OpenDroneMap
    • Cleaned code
    What did I do? Mostly, I got caught up with the project. I haven’t been very hands on since the python port, let alone the cmake branch, so I became a little more pythonistic by just trying to successfully modify the code.
    • I also added PDAL to the build processs
    • And I inserted PDAL into the point cloud translation process.

    Currently, this means we’ve dropped support for LAZ output, as I haven’t successfully built PDAL with LAZ support, but it stages the project for LAZ support through PDAL, and allows us to tap into additional PDAL functionality in the future.

    It was an intensive couple of days that would have been improved with French wine, but we were in Parma (Ohio). So, a shout out to the coders in Paris at the same time, and cheers to all.

    Posted in 3D, Drone, OpenDroneMap, OpenDroneMap, Photogrammetry, PMVS, UAS | Tagged: , | Leave a Comment »

    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 »

    Valentine’s map for my daughter

    Posted by smathermather on February 12, 2016

    It’s fun every now and then to play with Stamen Design’s Map Stack. Tonight as I was playing with it in part to wind down from a long work week, my 3-year-old requested a pink and purple map.

    So, a pink and purple map. I can do that:

    Screen Shot 2016-02-12 at 10.18.44 PM

    The cool thing about Map Stack, is I can share a link for my creation (click on the link above image), but I can also now embed this in a QGIS map.

    http://mapstack.stamen.com/edit.html#watercolor[sat=0];color:$ff3030[mask=!mapbox-water,comp=multiply,alpha=50];toner-hybrid;color:$8629a6[mask=!mapbox-water,comp=multiply,alpha=50];color:$60c9fe[mask=mapbox-water,comp=multiply,alpha=60]/2/30.1/-48.3
    

    I can embed the link to that map in a specially crafted XML file that can be loaded in QGIS or many projects that use the GDAL library:

    <GDAL_WMS>
        <Service name="TMS">
            <ServerUrl>http://a.sm.mapstack.stamen.com/((watercolor,$fff[hsl-saturation]),(mapbox-water,$ff3030[source-out@p])[multiply@50],toner-hybrid,(mapbox-water,$8629a6[source-out@p])[multiply@50],(mapbox-water,$60c9fe[source-in])[multiply@60])/${z}/${x}/${y}.png</ServerUrl>
        </Service>
        <DataWindow>
            <UpperLeftX>-20037508.34</UpperLeftX>
            <UpperLeftY>20037508.34</UpperLeftY>
            <LowerRightX>20037508.34</LowerRightX>
            <LowerRightY>-20037508.34</LowerRightY>
            <TileLevel>18</TileLevel>
            <TileCountX>1</TileCountX>
            <TileCountY>1</TileCountY>
            <YOrigin>top</YOrigin>
        </DataWindow>
        <Projection>EPSG:3857</Projection>
        <BlockSizeX>256</BlockSizeX>
        <BlockSizeY>256</BlockSizeY>
        <BandsCount>3</BandsCount>
        <Cache />
    </GDAL_WMS>
    
    Screen Shot 2016-02-12 at 10.34.16 PM

    View of map in QGIS

    
    
    Screen Shot 2016-02-12 at 10.33.37 PM

    View of map in QGIS reprojected to World Robison

    Posted in Other | Leave a Comment »

     
    Follow

    Get every new post delivered to your Inbox.

    Join 1,759 other followers