Shallow Thoughts : : Aug
Akkana's Musings on Open Source Computing and Technology, Science, and Nature.
Fri, 26 Aug 2016
I recently wrote about
Translating
track files between mapping formats like GPX, KML, KMZ and UTM
But there's one common mapping format that keeps coming up that's hard to
handle using free software, and tricky to translate to other formats:
ESRI shapefiles.
ArcGIS shapefiles are crazy. Typically they come as an archive that
includes many different files, with the same base name but different
extensions:
filename.sbn, filename.shx, filename.cpg, filename.sbx,
filename.dbf, filename.shp, filename.prj, and so forth.
Which of these are important and which aren't?
To be honest, I don't know. I found this description in my searches:
"A shape file map consists of the geometry (.shp), the spatial index
(.shx), the attribute table (.dbf) and the projection metadata file
(.prj)." Poking around, I found that most of the interesting metadata
(trail name, description, type, access restrictions and so on)
was in the .dbf file.
You can convert the whole mess into other formats using the
ogr2ogr
program. On Debian it's part of the gdal-bin
package. Pass it the .shp filename, and it will look in the same
directory for files with the same basename and other shapefile-related
extensions. For instance, to convert to KML:
ogr2ogr -f KML output.kml input.shp
Unfortunately, most of the metadata -- comments on trail conditions
and access restrictions that were in the .dbf file -- didn't make it
into the KML.
GPX was even worse.
ogr2ogr
knows how to convert directly to GPX,
but that printed a lot of errors like
"Field of name 'foo' is not supported in GPX schema. Use
GPX_USE_EXTENSIONS creation option to allow use of the <extensions>
element." So I tried
ogr2ogr -f "GPX" -dsco GPX_USE_EXTENSIONS=YES output.gpx input.shp
but that just led to more errors.
It did produce a GPX file, but it had almost no useful data in it,
far less than the KML did. I got a better GPX file by using ogr2ogr
to convert to KML, then using gpsbabel to convert that KML to GPX.
Use GeoJSON instead to preserve the metadata
But there is a better way: GeoJSON.
ogr2ogr -f "GeoJSON" -t_srs crs:84 output.geojson input.shp
That preserved most, maybe all, of the metadata the .dbf file and gave
me a nicely formatted file. The only problem was that I didn't have any
programs that could read GeoJSON ...
But JSON is a nice straightforward format, easy to
read and easy to parse, and it took surprisingly little work to add
GeoJSON parsing to
PyTopo.
Now, at least, I have a way to view the maps converted from shapefiles,
click on a trail and see the metadata from the original shapefile.
See also:
Tags: GIS, mapping, shapefiles, ESRI, ArcGIS, pytopo
[
12:11 Aug 26, 2016
More mapping |
permalink to this entry |
]
Wed, 17 Aug 2016
A few days ago I wrote about track files in maps, specifically
Translating
track files between mapping formats.
I promised to follow up with information on how to create new tracks.
Update: Years later, I added simple track editing to my own map program,
PyTopo.
It can split an existing track, or create a new track, then
can save as GPX.
For instance, I have some scans of old maps from the 60s and 70s
showing the trails in the local neighborhood. There's no newer
version. (In many cases, the trails have disappeared from lack of use
-- no one knows where they're supposed to be even though they're
legally trails where you're allowed to walk.) I wanted a way to turn
trails from the old map into GPX tracks.
My first thought was to trace the old PDF map. A lot of web searching
found a grand total of one page that talks about that:
How to convert image of map into vector format?.
It involves using GIMP to make an image containing just black lines
on a white background, saving as uncompressed TIFF, then using a series
of commands in GRASS. I made a start on that, but it was looking
like it might be a big job that way.
Since a lot of the old trails are still visible as faint traces in
satellite photos, I decided to investigate tracing satellite photos
in a map editor first, before trying the GRASS method.
But finding a working open source map editor turns out to be
basically impossible. (Opportunity alert: it actually wouldn't
be that hard to add that to PyTopo. Some day I'll try that, but
now I was trying to solve a problem and hoping not to get sidetracked.)
The only open source map editor I've found is called Viking, and it's
terrible. The user interface is complicated and poorly documented, and
I could input only two or three trail segments before it crashed
and I had to restart. Saving often, I did build up part of the trail
network that way, but it was so slow and tedious restoring between
crashes that I gave up.
OpenStreetMap has several editors available, and some of them are
quite good, but they're (quite understandably) oriented toward defining
roads that you're going to upload to the OpenStreetMap world map.
I do that for real trails that I've walked myself, but it doesn't seem
appropriate for historical paths between houses, some of which are now
fenced off and few of which I've actually tried walking yet.
Editing a track in Google Earth
In the end, the only reasonable map editor I found was Google Earth --
free as in beer, not speech.
It's actually quite a good track editor once I figured out how to use
it -- the documentation is sketchy and no one who writes about it tells
you the important parts, which were, for me:
Click on "My Places" in the sidebar before starting, assuming you'll
want to keep these tracks around.
Right-click on My Places and choose Add->Folder if
you're going to be creating more than one path. That way you can have
a single KML file (Google Earth creates KML/KMZ, not GPX) with all
your tracks together.
Move and zoom the map to where you can see the starting point for your path.
Click the "Add Path" button in the toolbar. This brings up a dialog
where you can name the path and choose a color that will stand out
against the map. Do not hit Return after typing the name --
that will immediately dismiss the dialog and take you out of path
editing mode, leaving you with an empty named object in your sidebar.
If you forget, like I kept doing, you'll have to right-click it and
choose Properties to get back into editing mode.
Iconify, shade or do whatever your window manager allows to get that
large, intrusive dialog out of the way of the map you're trying to edit.
Shade worked well for me in Openbox.
Click on the starting point for your path. If you forgot to move the
map so that this point is visible, you're out of luck: there's no way
I've found to move the map at this point. (You might expect something
like dragging with the middle mouse button, but you'd be wrong.)
Do not in any circumstances be tempted to drag with the left button
to move the map: this will draw lots of path points.
If you added points you don't want -- for instance, if you dragged on
the map trying to move it -- Ctrl-Z doesn't undo, and there's no Undo
in the menus, but Delete removes previous points. Whew.
Once you've started adding points, you can move the map using the arrow
keys on your keyboard. And you can always zoom with the mousewheel.
When you finish one path, click OK in its properties dialog to end it.
Save periodically: click on the folder you created in My Places and
choose Save Place As... Google Earth is a lot less crashy than
Viking, but I have seen crashes.
When you're done for the day, be sure to
File->Save->Save My Places.
Google Earth apparently doesn't do this automatically; I was forever
being confused why it didn't remember things I had done, and why every
time I started it it would give me syntax errors on My Places saying
it was about to correct the problem, then the next time I'd get the
exact same error. Save My Places finally fixed that, so I guess
it's something we're expected to do now and then in Google Earth.
Once I'd learned those tricks, the map-making went fairly quickly.
I had intended only to trace a few trails then stop for the night,
but when I realized I was more than halfway through I decided to push
through, and ended up with a nice set of KML tracks which I converted
to GPX and loaded onto my phone. Now I'm ready to explore.
Tags: mapping, GIS, openstreetmap
[
17:26 Aug 17, 2016
More mapping |
permalink to this entry |
]
Sun, 14 Aug 2016
I use map tracks quite a bit. On my Android phone, I use
OsmAnd, an excellent open-source
mapping tool that can download map data generated from
free OpenStreetMap,
then display the maps offline, so I can use them in places where
there's no cellphone signal (like nearly any hiking trail).
At my computer, I never found a decent open-source mapping program,
so I wrote my own, PyTopo,
which downloads tiles from OpenStreetMap.
In OsmAnd, I record tracks from all my hikes, upload the GPX files,
and view them in PyTopo. But it's nice to go the other way, too,
and take tracks or waypoints from other people or from the web and
view them in my own mapping programs, or use them to find them when hiking.
Translating between KML, KMZ and GPX
Both OsmAnd and PyTopo can show Garmin track files in the GPX format.
PyTopo can also show KML and KMZ files, Google's more complicated
mapping format, but OsmAnd can't. A lot of track files are distributed
in Google formats, and I find I have to translate them fairly often --
for instance, lists of trails or lists of waypoints on a new hike I plan
to do may be distributed as KML or KMZ.
The command-line gpsbabel
program does a fine job
translating KML to GPX.
But I find its syntax hard to remember, so I wrote a shell alias:
kml2gpx () {
gpsbabel -i kml -f $1 -o gpx -F $1:t:r.gpx
}
so I can just type
kml2gpx file.kml
and it will create
a
file.gpx for me.
More often, people distribute KMZ files, because they're smaller.
They're just gzipped KML files, so use "zip" and "unzip" to
unpack them. In Python you can use the zipfile module.
(Updated to reflect that it's zip, not gzip.)
Of course, if you ever have a need to go from GPX to KML, you can
reverse the gpsbabel arguments appropriately; and if you need KMZ,
run zip afterward.
UTM coordinates
A couple of people I know use a different format, called
UTM,
which stands for Universal Transverse Mercator, for waypoints,
and there are some secret lists of interesting local features passed
around in that format.
It's a strange system. Instead of using latitude and longitude like
most world mapping coordinate systems, UTM breaks the world into 60
longitudinal zones. UTM coordinates don't usually specify their zone
(at least, none of the ones I've been given ever have), so if someone
gives you a UTM coordinate, you need to know what zone you're in
before you can translate it to a latitude and longitude. Then a pair
of UTM coordinates specifies easting, and northing which
tells you where you are inside the zone. Wikipedia has a
map
of UTM zones.
Note that UTM isn't a file format: it's just a way of specifying two
(really three, if you count the zone) coordinates. So if you're given
a list of UTM coordinate pairs, gpsbabel doesn't have a ready-made way
to translate them into a GPX file. Fortunately, it allows a
"universal CSV"
(comma separated values) format, where the first line specifies
which field goes where. So you can define a UTM UniCSV format that looks
like this:
name,utm_z,utm_e,utm_n,comment
Trailhead,13,0395145,3966291,Trailhead on Buckman Rd
Sierra Club TH,13,0396210,3966597,Alternate trailhead in the arroyo
then translate it like this:
gpsbabel -i unicsv -f filename.csv -o gpx -F filename.gpx
I (and all the UTM coordinates I've had to deal with) are in zone 13,
so that's what I used for that example and I hardwired that into my
alias, but if you're near a zone boundary, you'll need to figure out
which zone to use for each coordinate.
I also know someone who tends to send me single UTM coordinate pairs,
because that's what she has her Garmin configured to show her.
For instance, "We'll be using the trailhead at 0395145 3966291".
This happened often enough, and I got tired of looking up the UTM UniCSV
format every time, that I made another shell function just for that.
utm2gpx () {
unicsv=`mktemp /tmp/point-XXXXX.csv`
gpxfile=$unicsv:r.gpx
echo "name,utm_z,utm_e,utm_n,comment" >> $unicsv
printf "Point,13,%s,%s,point" $1 $2 >> $unicsv
gpsbabel -i unicsv -f $unicsv -o gpx -F $gpxfile
echo Created $gpxfile
}
So I can say
utm2gpx 0395145 3966291
, pasting the two
coordinates from her email, and get a nice GPX file that I can push
to my phone.
What if all you have is a printed map, or a scan of an old map from
the pre-digital days? That's part 2, which I'll post in a few days.
Tags: mapping, GIS, osmand, openstreetmap
[
10:29 Aug 14, 2016
More mapping |
permalink to this entry |
]
Tue, 09 Aug 2016
A couple of days ago we had a spectacular afternoon double rainbow.
I was out planting grama grass seeds, hoping to take take advantage of
a rainy week, but I cut the planting short to run up and get my camera.
And then after shooting rainbow shots with the fisheye lens,
it occurred to me that I could switch to the zoom and take some
hummingbird shots with the rainbow in the background. How often
do you get a chance to do that? (Not to mention a great excuse not to
go back to planting grass seeds.)
(Actually, here, it isn't all that uncommon since we get a lot of
afternoon rainbows. But it's the first time I thought of trying it.)
Focus is always chancy when you're standing next to the feeder,
waiting for birds to fly by and shooting whatever you can.
Next time maybe I'll have time to set up a tripod and remote
shutter release. But I was pretty happy with what I got.
Photos:
Double rainbow, with hummingbirds.
Tags: nature, birds, rainbow, photography
[
19:40 Aug 09, 2016
More nature |
permalink to this entry |
]
Sat, 06 Aug 2016
I have a little browser script in Python, called
quickbrowse,
based on Python-Webkit-GTK. I use it for things like quickly calling
up an anonymous window with full javascript and cookies, for when I
hit a page that doesn't work with Firefox and privacy blocking;
and as a quick solution for calling up HTML conversions of doc and pdf
email attachments.
Python-webkit comes with a simple browser as an example -- on Debian
it's installed in /usr/share/doc/python-webkit/examples/browser.py.
But it's very minimal, and lacks important basic features like
command-line arguments. One of those basic features I've been meaning
to add is Back and Forward buttons.
Should be easy, right? Of course webkit has a go_back() method, so
I just have to add a button and call that, right? Ha. It turned out to
be a lot more difficult than I expected, and although I found a fair
number of pages asking about it, I didn't find many working examples.
So here's how to do it.
Add a toolbar button
In the WebToolbar class (derived from gtk.Toolbar):
In __init__()
, after initializing the parent class and
before creating the location text entry (assuming you want your
buttons left of the location bar), create the two buttons:
backButton = gtk.ToolButton(gtk.STOCK_GO_BACK)
backButton.connect("clicked", self.back_cb)
self.insert(backButton, -1)
backButton.show()
forwardButton = gtk.ToolButton(gtk.STOCK_GO_FORWARD)
forwardButton.connect("clicked", self.forward_cb)
self.insert(forwardButton, -1)
forwardButton.show()
Now create those callbacks you just referenced:
def back_cb(self, w):
self.emit("go-back-requested")
def forward_cb(self, w):
self.emit("go-forward-requested")
That's right, you can't just call go_back on the web view, because
GtkToolbar doesn't know anything about the window containing it.
All it can do is pass signals up the chain.
But wait -- it can't even pass signals unless you define them.
There's a __gsignals__
object defined at the beginning
of the class that needs all its signals spelled out.
In this case, what you need is
"go-back-requested": (gobject.SIGNAL_RUN_FIRST,
gobject.TYPE_NONE, ()),
"go-forward-requested": (gobject.SIGNAL_RUN_FIRST,
gobject.TYPE_NONE, ()),
Now these signals will bubble up to the window containing the toolbar.
Handle the signals in the containing window
So now you have to handle those signals in the window.
In WebBrowserWindow (derived from gtk.Window), in __init__
after creating the toolbar:
toolbar.connect("go-back-requested", self.go_back_requested_cb,
self.content_tabs)
toolbar.connect("go-forward-requested", self.go_forward_requested_cb,
self.content_tabs)
And then of course you have to define those callbacks:
def go_back_requested_cb (self, widget, content_pane):
# Oops! What goes here?
def go_forward_requested_cb (self, widget, content_pane):
# Oops! What goes here?
But whoops! What do we put there? It turns out that WebBrowserWindow
has no better idea than WebToolbar did of where its content is or
how to tell it to go back or forward.
What it does have is a ContentPane (derived from gtk.Notebook),
which is basically just a container with no exposed methods that
have anything to do with web browsing.
Get the BrowserView for the current tab
Fortunately we can fix that. In ContentPane, you can get the current
page (meaning the current browser tab, in this case); and each page
has a child, which turns out to be a BrowserView.
So you can add this function to ContentPane to help other classes
get the current BrowserView:
def current_view(self):
return self.get_nth_page(self.get_current_page()).get_child()
And now, using that, we can define those callbacks in WebBrowserWindow:
def go_back_requested_cb (self, widget, content_pane):
content_pane.current_view().go_back()
def go_forward_requested_cb (self, widget, content_pane):
content_pane.current_view().go_forward()
Whew! That's a lot of steps for something I thought was going to be
just adding two buttons and two callbacks.
Tags: python, programming, webkit, gtk
[
16:45 Aug 06, 2016
More programming |
permalink to this entry |
]