2019 (old posts, page 7)

Road trip recap 1: Fort Collins to Moab

Earlier this month, My family and I set off on a road trip through southeastern Utah and southwestern Colorado with friends from Montpellier, France. Our itinerary: Arches National Park, Mesa Verde National Park, Durango, Silverton, and Great Sand Dunes National Park. One of our friends had been to the United States twenty years ago, the other three, never. They spent a week in New York City before Ruth picked them up at Denver International Airport. We hit the road the next day, all 8 of us in a 2016 Honda Odyssey, loaded with optimism, good intentions, and Harry Potter audiobooks.

https://live.staticflickr.com/65535/48580595166_63a2157b7a_b.jpg

View from the 3rd row of our Honda Odyssey, leaving Fort Collins, CO

Configured for this road trip, our Odyssey has 3 rows of seats, 8 in all, with 31 cubic feet of cargo space. We had another 11 cubic feet in our ski carrier. This turned out to be completely adequate for 4 adults and 4 kids. We had enough room in back for a mid-sized cooler and bags. Outdoor gear like extra shoes, hats, picnic blankets, &c went up top. We soon settled on a formation of three kids in the back row, 2 adults and one kid in the second row, and 2 adults up front. Fully loaded like this, we got about 25 miles per gallon. When you multiply this by 8, 200 person-miles per gallon isn't bad mileage. The curb weight of the Odyssey is about 4500 pounds. Human weight was another 1000 pounds. And then we probably had another 200 pounds of gear, food, water, and ice. We've never asked so much of our car and it did fine. It affords good views, has a small sunroof, the seats are comfortable. The one drawback is that when it comes to listening to music or audiobooks, the occupants of the 3rd row seats depend on the speakers at the feet of their family in the 2nd row. In the 2nd you get blasted and in the 3rd it's not quite loud enough.

Much of the time on this trip we listened to Bernard Giraudeau read the first three Harry Potter novels. My French is just good enough to follow along and I loved having native French speakers along to explain the subtle details, such as that Giraudeau's impression of Gilderoy Lockhart used an obviously false Languedoc accent. It seemed a weird to me, too.

Our friends speak English as well as I speak French, but as their kids are beginners in English and Ruth, Arabelle, and Bea are quite fluent in French, we mainly used French. I spoke French every day of the trip, and not just with our friends: we were among French or French-Canadian tourists everywhere we went.

The drive from Fort Collins to Moab is long and we broke it up by stopping for a night in Glenwood Springs, a tourist town at the west end of a beautiful canyon. The next morning we stopped shortly after in Palisade to show our friends some orchards and buy fresh peaches and nectarines directly from the producers. Abundant sunshine, water from the Colorado River, and a relatively (for Colorado) frost-free microclimate make Palisade one of Colorado's best places to grow fruit. It's a location not unlike the Terasses du Larzac in the south of France, where cool air descending from the nearby cliffs keeps grapes from stewing in their skins after the sun goes down.

https://live.staticflickr.com/65535/48580742982_8f9d7a8098_b.jpg

Mt. Garfield and peach trees, Palisade, CO

https://live.staticflickr.com/65535/48580743032_6dee6367c0_b.jpg

Palisade peaches

After leaving Palisade, Grand Junction, and Fruita behind we crossed into Utah and then left I-70 to take the scenic route along the Colorado River to Moab, Utah's State Route 128. We picnicked at the old Dewey Bridge site, stopped briefly at the base of the Fisher Towers, and enjoyed the rise of the canyon walls as we approached Moab. I hadn't been on SR-128 since 1992 and it was as beautiful as I remembered. Our friends were gobsmacked by the colors. I still am, and I've been exploring Southern Utah for fifty years. It's a uniquely beautiful part of the world.

https://live.staticflickr.com/65535/48601723286_67f901992b_b.jpg

Fisher Towers, Utah

Bonne rentrée

Today is the first day of the 2019-2020 school year for French kids. Here's a photo from Bea's rentrée, 2016.

https://live.staticflickr.com/65535/48666176571_79bca797f7_b.jpg

Vacation

My family and I are taking friends from Montpellier (France) on a Colorado and Utah road trip and I'll be away from work and open source projects until the 16th.

Rasterio 1.0.25

I released Rasterio 1.0.25 yesterday. It has a few important bug fixes, but the core of the work was writing and testing shims to make the package compatible with GDAL version 3.0 and PROJ version 6. Norman Barker did much of that work and I only had to make sure that we were using the right coordinate axis order strategy everywhere and figure out which of the output changes were new Rasterio bugs and which were actually improvements delivered by PROJ 6.

Please note that the binary wheels for 1.0.25 on PyPI contain GDAL 2.4.2, not 3.0, and that no new features of GDAL 3 and PROJ 6 are intentionally exposed in Rasterio's API. My wheel builds are already running up against time limits on Travis CI, and GDAL 3 and PROJ 6 take even longer to compile. My system is going to need some more hacks before Rasterio wheels with the latest GDAL and PROJ are possible. You might be able to get a combination of Rasterio 1.0.25, GDAL 3, and PROJ 6 from conda-forge. I look forward to hearing how that works for users.

I hope you'll appreciate that I've managed to shrink the size of the GDAL shared libraries in the manylinux wheels by 50%. I wish I knew why they are so much bigger than the OS X libraries. I suspect it's due to the ancient toolchain and glibc used by manylinux1.

Never Summer 100K volunteering

In July the the Gnar Runners put on a race in State Forest State Park, the Never Summer 100K. It's a 64 mile loop through the Never Summer and Rawah ranges and the Colorado State Forest. The race requires park rangers, emergency medical technicians, ham radio operators, and lots of volunteer bodies.

https://live.staticflickr.com/65535/48400926322_fa06cb8031_b.jpg

Lulu and Thunder mountains, and moose, from Cameron Pass at 7 a.m.

In 2018 I flipped burgers at the finish line so that runners could get a hot meal in the middle of the night after being on the trail for twenty hours or more. In 2019, I spent an afternoon and evening supporting runners at the "Canadian" aid station at the race's 50 mile mark. The aid station gets its name from being near Never Summer Nordic's North Fork Canadian Yurt near the North Fork of the Canadian River, a tributary of the North Platte River. We had Canadian flags and a life-sized cutout of Justin Trudeau. At some point there was consensus that next year Ryan Gosling should join us.

https://live.staticflickr.com/65535/48400926962_0a3bf8359a_b.jpg

Canadian aid station ready for runners to arrive, 2 p.m.

I had a cold, so I didn't cook or handle food, but I did a little of everything else. I hauled gear, deployed a portable toilet, set up canopies, organized and fetched drop bags, helped runners arrive, recover, and head out again. After the 1 a.m. cutoff, I helped break down the aid station and pack it all up. I was out on the course for 15 hours, 3 hours more than the first placed runner, but 8 hours less than the last finisher.

https://live.staticflickr.com/65535/48400786551_b1a322132f_b.jpg

Last of the day's rain showers, 8 p.m.

We could see rain on the course as early as 11 a.m. None fell at the Canadian aid station until about 4 p.m., but then it rained until just before sunset. We had a pretty solid shower of small hail as well. Runners arriving in this rain were pretty worn out from a 6 mile slog through heavy mud. Some contemplated dropping out and a few did. Most found the energy to continue after some food, a cup of ramen or tea, and a couple minutes out of the rain. It was, after all, only 14 miles to the finish and there was plenty of time left.

https://live.staticflickr.com/65535/48400784606_e4d737a156_b.jpg

Sunset and mud puddles

I felt good helping runners accomplish their goal and had a great time hanging out with other volunteers, many of them experienced ultra-runners, and listening to their stories. I wish I'd spent more time with the ham radio team and learned more about packet radio and running a network in the backcountry.

Race director Nick Clark's official recap of the race is here: http://gnarrunners.com/2019/08/a-recap-of-the-2019-never-summer-100km/.

Man Walks on Moon!

I'm blogging this a day early because I expect to be too busy tomorrow. My Mom recently sent me her copy of the Detroit Free Press, her hometown paper, from Monday, July 21, 1969, the day of my birth.

https://live.staticflickr.com/65535/48333500016_7e363fedbe_b.jpg

I've enjoyed being connected to this milestone in space exploration and have been nuts about space for as long as I can remember. I wouldn't trade my birthday for any other.

Running in the Cascades

Today was my first full day back in Colorado after a week in Washington with Ruth's clan. My phone stopped charging during the trip and so I didn't take as many pictures of the Cascades as I would have liked. I took a few during a short and steep run up a fire road near our rental house on Sunday and this is the best.

https://live.staticflickr.com/65535/48261860277_62412f8a6b_b.jpg

Looking down Bear Creek to Cle Elum Lake

I didn't carry my phone on my longest run into the Alpine Lakes Wilderness upstream from Cle Elum Lake and have to content myself with memories. According to Forest Service web pages, much of the area is covered by old growth, never logged, forest. Indeed, I saw many titanic Douglas firs around and above Pete Lake. Such giant trees are very rare in Colorado.

Offseason running

After slacking off for a couple weeks after Quad Rock, I've succeeded at getting back to 30+ miles per week. I'm running 3 times Monday through Friday and doing one long easy pace run on the weekend. Starting July 1, I'll start to build towards races in September and October.

I strained some muscle in my back 3 weeks ago and this has forced me to pay more attention to my running form. Engaging my glutes and hips and trying to get my upper body to float was a good way to minimize the pain in my back and is something I'm going to continue to practice.

Busier

This week will be busier than the last! I'm writing this from the neighborhood pool where Bea is training with her swim team. It's sunny and mild and it feels great to be writing outside.

Fix for Shapely's GEOS library loading bug in 1.7a2

Including GDAL and GEOS in binary wheels that go to the Python Package Index (PyPI) has been good for users of fiona, rasterio, and shapely, but has also exposed Python GIS programmers on OS X to a perplexing bug. Shapely's issue #553 has been weighing on me for a long time. It's been hard to understand, hard to explain, and bites very hard. The short program below will trigger the bug on any version of OS X with any version of fiona and any version of shapely before 1.7a2 which is installed using a wheel from PyPI.

# crash.py

import fiona
from shapely.geometry import Point
from shapely.ops import unary_union


print("Buffering points to create polygons")
SHAPES = [Point(i, i).buffer(1.5) for i in range(20)]

print("Computing union of polygons")
union = unary_union(SHAPES)

print("Union: {}".format(union))

For example:

$ python crash.py
Buffering points to create polygons
Computing union of polygons
Assertion failed: (!static_cast<bool>("should never be reached")), function itemsTree, file AbstractSTRtree.cpp, line 373.
Abort trap: 6

The unary_union function uses a GEOS STRtree and an assertion that should never be reached is in fact reached and the program aborts.

There has been a work-around – import shapely before importing other modules that are dynamically linked with GEOS, such as fiona and rasterio – but that's smelly and hard to ensure in a complex project.

The problem exists in a small niche: one operating system and one kind of program, one that that loads GEOS twice in different ways. Once when fiona's extension modules are loaded and cause the linked GEOS library to be loaded as with any compiled program, and once again when shapely is imported and it calls dlopen from python to load GEOS. I've found no diagnosis or solution of a similar problem on the web. Was the problem in GEOS? Was it in the library files that I built for the fiona, rasterio, and shapely wheels? Was the bug in delocate, the program I use to find dependencies and bundle them into the wheels? I asked GEOS developers and folks at work, to no avail. I was stumped for a long time.

I asked Even Rouault about the issue and he suggested a solution might be found in being more careful about using the correct mode with dlopen and he offered some example Python code that included this: handle = CDLL(None). My first thought was "can we use the ctypes CDLL class like this?" The docs say that the first argument is a path to a library file and that's how I've been using it in Shapely.

While looking for guidance on passing None to the CDLL constructor, I found some commented code in https://eli.thegreenplace.net/2013/03/09/python-ffi-with-ctypes-and-cffi.

# CDLL(None) invokes dlopen(NULL), which loads the currently running
# process - in our case Python itself. Since Python is linked with
# libc, readdir_r will be found there.
# Alternatively, we can just explicitly load 'libc.so.6'.
lib = CDLL(None)

The OS X dlopen man page doesn't exactly say that,

dlopen() examines the mach-o file specified by path. If the file is compatible with the current process and has not already been loaded into the current process, it is loaded and linked. After being linked, if it contains any initializer functions, they are called, before dlopen() returns. dlopen() can load dynamic libraries and bundles. It returns a handle that can be used with dlsym() and dlclose(). A second call to dlopen() with the same path will return the same handle, but the internal reference count for the handle will be incremented. Therefore all dlopen() calls should be balanced with a dlclose() call.

If a null pointer is passed in path, dlopen() returns a handle equivalent to RTLD_DEFAULT.

but the POSIX spec is clear that dlopen(NULL) returns a handle to a global symbols object and that's what I observe on my MacBook. Here's an interpreter session where I import fiona and look in the global symbols object for a GDAL C API function.

>>> import fiona
>>> from ctypes import CDLL
>>> handle = CDLL(None)
>>> handle.GDALGetDriverCount
<_FuncPtr object at 0x111a3eb38>
>>> handle.GDALGetDriverCount()
187

When fiona is imported, Python calls dlopen with the path to fiona's extension (.so) modules, and as these are liked with libgdal, the GDAL library is loaded. Now, libgdal is linked with libgeos_c, so will we find GEOS C API functions in the global symbols object?

>>> handle.initGEOS
<_FuncPtr object at 0x111a3fb38>

Yes. This changes everything. The problem that has bedeviled shapely users is caused by using dlopen to load a library that's already been loaded. We can avoid the problem by detecting whether libgeos_c has already been loaded and skipping the troublesome dlopen call. I've done this for shapely and made a new 1.7 pre-release. Please considering running pip install -U --pre shapely to try it out.

The OS X man page for dlsym says that looking up functions in the global handle is the slowest possible approach, but ctypes does cache the addresses of functions it finds and my test programs don't run measurably slower, so this just might work out.

I'd like to understand why this problem doesn't also occur on Linux. Is it because of differences in the library loaders or the executable file formats themselves? Is there subtle platform-specific bug in GEOS? I'll write more if I get to the bottom of this.