<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>perrygeo.com</title><link href="https://www.perrygeo.com/" rel="alternate"></link><link href="https://www.perrygeo.com/atom.xml" rel="self"></link><id>https://www.perrygeo.com/</id><updated>2024-11-20T00:00:00-07:00</updated><subtitle>Data, Software, Systems</subtitle><entry><title>Clojure is for optimists - notes on the 2024 Conj</title><link href="https://www.perrygeo.com/clojure-is-for-optimists-notes-on-the-2024-conj.html" rel="alternate"></link><published>2024-11-20T00:00:00-07:00</published><updated>2024-11-20T00:00:00-07:00</updated><author><name>Matthew T. Perry</name></author><id>tag:www.perrygeo.com,2024-11-20:/clojure-is-for-optimists-notes-on-the-2024-conj.html</id><summary type="html">&lt;p&gt;I started playing with Clojure in 2013 and was immediately impressed.
It had the killer combination of &lt;strong&gt;immutable data structures&lt;/strong&gt;, &lt;strong&gt;functional programming&lt;/strong&gt;, 
an &lt;strong&gt;interactive REPL&lt;/strong&gt;, and &lt;strong&gt;structural editing&lt;/strong&gt;.
I dabbled a bit, created a few demos, fell in love with the language, evangelized everywhere I could, 
then went back to …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I started playing with Clojure in 2013 and was immediately impressed.
It had the killer combination of &lt;strong&gt;immutable data structures&lt;/strong&gt;, &lt;strong&gt;functional programming&lt;/strong&gt;, 
an &lt;strong&gt;interactive REPL&lt;/strong&gt;, and &lt;strong&gt;structural editing&lt;/strong&gt;.
I dabbled a bit, created a few demos, fell in love with the language, evangelized everywhere I could, 
then went back to writing OtherLang because that's what pays the bills.
This is a common sentiment, as I came to learn.
Clojure is definitely a niche language, but the uncompromising focus on both quality and fun seems to compel programmers to use it.&lt;/p&gt;
&lt;p&gt;Ten years later, for no reason in particular, I opened up my old Clojure projects and dove in again.
The ecosystem had changed a bit: We now had &lt;code&gt;deps.edn&lt;/code&gt; as an alternative to Leiningen (which I did not enjoy), an LSP, a linter, and shadow-cljs to name a few.
But my decade-old code still "just worked" for the most part; the changes were limited to the outer layer, the developer experience.
The core language is stable, a breath of fresh air!&lt;/p&gt;
&lt;p&gt;So I decided to attend the 2024 Clojure Conj on my own dime. And I'm glad I did!
I found a friendly community of thoughtful folks eager to talk about Clojure 
and how they're applying it in the wild. &lt;/p&gt;
&lt;p&gt;The videos for all 2024 talks are now &lt;a href="https://www.youtube.com/playlist?list=PLZdCLR02grLr4TWUP6qeLxIn4OJLNwKNZ"&gt;available on youtube&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;Here's a minimally-edited summary of my notes of the experience, better late than never...&lt;/p&gt;
&lt;h2&gt;Venue&lt;/h2&gt;
&lt;p&gt;On day 1, I was convinced I'd shown up at the wrong place. The George Washington Masonic Memorial 
is an impressive building and not your typical conference venue! Very cool.&lt;/p&gt;
&lt;p&gt;&lt;img src="images/GeorgeWashingtonMemorial.jpg"&gt;&lt;/p&gt;
&lt;h2&gt;The amazing day of datomic&lt;/h2&gt;
&lt;p&gt;One of my primary quests was to learn everything I could about Datomic, and evaluate it as an alternative to Postgresql for certain use cases.
Datomic Pro is now Apache licensed, free to use by anyone. However, that's only the compiled jar file; the source code isn't available. And I'm fine with that, open-source purists can make that decision for themselves.&lt;/p&gt;
&lt;p&gt;The core idea of Datomic can be summarized as an unbundled, immutable database with identity and time as first class citizens.
Let's break that down:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Datomic is &lt;strong&gt;unbundled&lt;/strong&gt;: the writer, the readers, and the storage layer are separate components.
  Rather than a centralized database server that serves and coordinates &lt;em&gt;all&lt;/em&gt; client queries, Datomic has a peer architecture.
  You run queries directly in your application process, allowing your app to hit storage directly &lt;em&gt;without going through a central server&lt;/em&gt;.
  Combined with a storage engine like DynamoDB, this gives you reads that can be horizontally scaled.
  The write load continues to be served by a single server in charge of building efficient covering indexes and notifying peers, known as the "transactor".&lt;/li&gt;
&lt;li&gt;It is &lt;strong&gt;immutable&lt;/strong&gt; in that it acts as a ledger. You don't delete data, you assert new facts and retract old facts. Those facts &lt;em&gt;never&lt;/em&gt; change.&lt;/li&gt;
&lt;li&gt;It uses an &lt;strong&gt;EAVT&lt;/strong&gt; data model where every fact is a tuple of entity, attribute, value and time. The notion of time and identity allows datomic to describe a full history instead of just the current mutable state. You can time travel to any point and compare values across time. &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We learned how to set up a schema and transact new entities,
how to write queries with &lt;a href="https://www.learndatalogtoday.org"&gt;Clojure's flavor of datalog&lt;/a&gt;,
discussed some more complex topics around architecture and performance.&lt;/p&gt;
&lt;h2&gt;Day 2&lt;/h2&gt;
&lt;p&gt;Day two started with more datomic, this time a deap dive into the architecture and some new
observabilty features: &lt;code&gt;io-stats&lt;/code&gt; for disk IO and &lt;code&gt;tx-stats&lt;/code&gt; for transaction timing.
It also introduced to me the idea of &lt;strong&gt;squiids&lt;/strong&gt;, sequential UUIDs, which sort by time but still looks like an arbitrary uuid4.
This allows for serial id-like performance (index locality) without exposing an integer that reveals the size of your database. 
This sounds like a perfect compromise to the bigint vs uuid tradeoff that most postgres users face.&lt;/p&gt;
&lt;p&gt;There was a surprising amount of discussion of Python, mainly in the context of machine learning.
ML/AI is fully dominated by Python and even shops that are all-in on Clojure still work with
models trained in Python. I learned about ONNX, a standard interchange format which allows 
to package up a Python model (sklearn or pytorch) and run the inference engine in another language.&lt;/p&gt;
&lt;p&gt;I learned of &lt;code&gt;test.contract&lt;/code&gt; an impressive library for mocking external systems.
This is ideal for &lt;a href="https://www.youtube.com/watch?v=4fFDFbi3toc"&gt;deterministic simulation testing of distributed systems&lt;/a&gt;
Though I'll still strongly advocate for more integration and e2e tests, 
this talk gave me a glimpse of more advanced unit testing techniques that might come in handy some day.&lt;/p&gt;
&lt;p&gt;I was introduced to the &lt;code&gt;standard-clj&lt;/code&gt; formatter which aims to be the canonical formatter. I'm not sure how it compares to &lt;code&gt;cljfmt&lt;/code&gt; which I current use.&lt;/p&gt;
&lt;p&gt;Arne gave a wonderful lightning demo of &lt;a href=""&gt;Overtone&lt;/a&gt;, a music composition software library for Clojure.
If you've never seen anyone create a live music experience with an emacs REPL, it's hard to describe. As I'm
currently studying music theory, I plan on diving into overtone with gusto when I return. Inspirational.&lt;/p&gt;
&lt;p&gt;Luke gave a wonderful overview of resource description framework (RDF) 
and the current state of LLMs. Yes, the same RDF that underpinned the 
"Semantic Web" ideas (remeber that?). 
The thesis was compelling: LLMs could use RDF to gain accuracy and logical consistency.
He pointed out an &lt;a href="https://karpathy.ai/zero-to-hero.html"&gt;AI-from-scratch tutorial&lt;/a&gt; that seems comprehensive
and mentioned the seminal paper that started this whole AI gold rush: &lt;a href="https://arxiv.org/pdf/1706.03762"&gt;Attention is all you need&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Probably my favorite talk of the conference, Paula gave us an experience report
of porting the &lt;code&gt;clojure.java.math&lt;/code&gt; library to ClojureScript.
This meant navigating licenses and IP concerns, the Clojure patch process,
and reimplementing many of the core math functions.
Most impressive was the test harness for thoroughly testing &lt;code&gt;cljs-math&lt;/code&gt; against its 
Clojure sibling. Very impressive work, and really embodied the community focus on quality, stability,
and keeping parity between the two main hosts platforms (Java and Javascript). &lt;/p&gt;
&lt;h2&gt;Day 3&lt;/h2&gt;
&lt;p&gt;Rich Hickey started the day with a brief but inspriring message.
He's on his way to retirement and seems in many ways to be "passing the torch" to others in the community.
And his message was simple: &lt;strong&gt;Clojure is for optimists&lt;/strong&gt;,
expert programmers who take pride in the quality of their work and
face down gnarly problems with intellectual curiosity and a good attitude.
It's a concise and accurate way to describe the vibe of the community.&lt;/p&gt;
&lt;p&gt;I learned about Kevel's architecture of distributed system on AWS.
Definitely worth a re-watch, this is an experience report that carries lots of valuable insigh.
There was a strong emphasis on causality, which I appreciated.
More background reading on &lt;a href="https://repositorio-aberto.up.pt/bitstream/10216/151975/2/636689.pdf"&gt;causal consistency in the cloud&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Scientific clojure has a real potential. It's got interoperability with Python, Wolfram, etc. pluys a
native clojure tool ecosystem that covers much of modern "data science" stack.
All the pieces are there, a la carte in true Clojure fashion.&lt;/p&gt;
&lt;p&gt;We got a great Metabase experience report. I learned all about pet birds, astrology signs, and which crystals to put on your desk to enhance your energy flows.
Seriously. The opening 5 minutes of the talk might have been the hardest I've ever laughed at a conference.
Standup commedy gold. Eventually talk turned back to Clojure and I got to hear an experience report that
gave me a taste of some of the challenges faced by teams on large clojure codebases.&lt;/p&gt;
&lt;p&gt;The Clojure Camp folks and their presentation was inspiring. Despite Clojure being an unabashedly
expert-oriented tool, there is an unmistakable community commitment to welcoming newbies and
taking learning seriously.&lt;/p&gt;
&lt;p&gt;Colin Fleming, the author of Cursive, demonstrated how LLMs could be more tightly integrated into our IDEs, as well as some of their drawbacks.
Once again, the tone was pleasantly objective: no over-hyping nor hating on AI, just solid observations. 
The &lt;a href="https://caveman.mccue.dev/"&gt;Caveman&lt;/a&gt; web framework/tutorial was released a few days earlier;
he showed how Claude sonnet could follow the tutorial and build the software from English instructions. Impressive!&lt;/p&gt;
&lt;p&gt;To end the conference, Alex Miller gave an update to last year's keynote, &lt;a href="https://www.youtube.com/watch?v=c5QF2HjHLSE&amp;amp;list=PLZdCLR02grLpIQQkyGLgIyt0eHE56aJqd"&gt;Design in practice&lt;/a&gt;. 
It's a strategy for a software design process with six steps:
&lt;strong&gt;Describe&lt;/strong&gt;, &lt;strong&gt;Diagnose&lt;/strong&gt;, &lt;strong&gt;Delimit&lt;/strong&gt;, &lt;strong&gt;Direction&lt;/strong&gt;, &lt;strong&gt;Design&lt;/strong&gt;, then &lt;strong&gt;Develop&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Why front-load the thinking, as opposed to simply sprinting away?&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;"The solution should feel obvious once you've written the right problem statement" - Rich Hickey (via Alex)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The design process and the presentation itself was in Google Sheets! Definitely the first time I've seen a slideshow embedded in a spreadsheet.
It was a fascinating glimpse into the inner workings of the Clojure language design process leading up to the 1.12 release,
which emphasizes clear thinking &lt;em&gt;and&lt;/em&gt; real-world feedback loops.
Notice that writing production code is the final step and you can backtrack anytime;
I can't see many Agile/Scrum shops engaging
with such an extended planning process but it evidently yields high quality software.&lt;/p&gt;
&lt;h2&gt;Fin&lt;/h2&gt;
&lt;p&gt;I came away from the conference with renewed enthusiasm, not just for Clojure but for the field of 
software engineering as a whole.
The 2024 zeitgeist would say the software industry is in crisis: "the job market is terrible" and "AI is going to take our jobs".
Seems as though software projects are increasingly drowning in unbounded cloud complexity on one hand, and the weight of legacy technical decisions on the other.&lt;/p&gt;
&lt;p&gt;But people have been saying that about software for decades! 
Is it incompetent programmers or bad managers or greedy executives or the technology they picked? Who knows.
The majority of software projects still fail and we don't really know why.&lt;/p&gt;
&lt;p&gt;But functional programming, and Clojure in particular,
offer a viable alternative to the madness, a way &lt;a href="https://curtclifton.net/papers/MoseleyMarks06a.pdf"&gt;out of the tarpit&lt;/a&gt;.
It's not a silver bullet of course, no technology is. But in the hands of a talented and motivated group of developers,
you &lt;em&gt;can&lt;/em&gt; build challenging software systems to very high standards with Clojure. And have fun doing it.
See you at Clojure/Conj 2025!&lt;/p&gt;</content><category term="articles"></category><category term="Clojure"></category><category term="GPU"></category><category term="Math"></category><category term="LinearAlgebra"></category></entry><entry><title>Measuring the GPU/CPU tradeoff</title><link href="https://www.perrygeo.com/measuring-the-gpucpu-tradeoff.html" rel="alternate"></link><published>2024-09-17T00:00:00-06:00</published><updated>2024-09-17T00:00:00-06:00</updated><author><name>Matthew T. Perry</name></author><id>tag:www.perrygeo.com,2024-09-17:/measuring-the-gpucpu-tradeoff.html</id><summary type="html">&lt;p&gt;There's a lot of well-deserved excitement around graphics processing units (GPU), not just in the stock market but the software world too. The success stories are too good to ignore. I've heard GPUs described as an "accelerant", and one might get the impression that sprinkling a little GPU on a …&lt;/p&gt;</summary><content type="html">&lt;p&gt;There's a lot of well-deserved excitement around graphics processing units (GPU), not just in the stock market but the software world too. The success stories are too good to ignore. I've heard GPUs described as an "accelerant", and one might get the impression that sprinkling a little GPU on a project will be magic pixie dust to make it go faster. It's also likely to explode if you're not careful.&lt;/p&gt;
&lt;p&gt;There are obvious and impressive applications in graphics, gaming, simulation, statistics, deep learning - any field that requires heavy number crunching with linear algebra routines. And these are many of the hottest fields in computing at the moment for a reason.&lt;/p&gt;
&lt;p&gt;But there is a downside for software developers.
In order to see any benefit at all from a GPU and to avoid wasting time, energy and money,
you need to &lt;strong&gt;keep the hardware busy with large yet simple math problems&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src="images/brr.png"&gt;&lt;/p&gt;
&lt;p&gt;Not all software is amenable to this style of programming.
If not, the humble and ubiquitous CPU outperforms the GPU in many meaningful ways. But how do you know?&lt;/p&gt;
&lt;p&gt;In this article, I'll demonstrate a workflow using matrix multiplication, perhaps the best example of a simple but time-consuming math problem. When you see a problem successfully leverage a GPU, it's a good bet matrix multiplication is involved. Or put another way, if you can express your problem as a matrix multiplication, there's a good bet GPUs will help you.&lt;/p&gt;
&lt;p&gt;We'll generate random matrices of varying sizes, then perform multiplication using both the CPU and GPU. By timing the simplest of operations carefully, we can make some inferences about the behavior of the two processing units, and how that guides hardware decisions.&lt;/p&gt;
&lt;h2&gt;Core questions&lt;/h2&gt;
&lt;p&gt;Audience: devs faced with a decision to potentially add GPU to the mix.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Will a GPU make my numeric code faster, in clock time? Under all inputs?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;How much time does the numeric code take relative to the rest of the overall process?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Will a GPU be &lt;em&gt;economically&lt;/em&gt; efficient, in operations per dollar?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Will the burden of GPU hardware drivers and the relative scarcity of hardware itself be an issue?&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If you're already deep into it or have to use it, you know this already. 
Questions about why? This is a great dive into the details:
&lt;a href="https://horace.io/brrr_intro.html"&gt;Making Deep Learning Go Brrrr From First Principles&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I'm not going to cover deep CUDA knowledge or details of how to implement various algorithms with it. 
The implementation is yours. That's the point - in order to make this assessment, you need to test real code on real hardware.&lt;/p&gt;
&lt;h2&gt;Matrix math in clojure&lt;/h2&gt;
&lt;p&gt;In order to perform identical matrix multiplication operations using both the CPU and GPU,
I've chosen to use the Clojure &lt;a href="https://neanderthal.uncomplicate.org/"&gt;Neanderthal&lt;/a&gt; library.&lt;/p&gt;
&lt;p&gt;Why? Neanderthal allows you to write high-level code for GPU and CPU in the same language.
While not identical, the code is very similar across the platforms. This lets us translate easily
between GPU and CPU code, meaning we only have to figure the logic once.&lt;/p&gt;
&lt;p&gt;Here are the clojure dependencies, typically found in a &lt;code&gt;deps.edn&lt;/code&gt; file.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;;; deps.edn&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:paths&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;src&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:deps&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="c1"&gt;;; clojure itself is just a library&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nv"&gt;org.clojure/clojure&lt;/span&gt;&lt;span class="w"&gt;               &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:mvn/version&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;1.12.0-alpha12&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;;; linear algebra&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nv"&gt;uncomplicate/neanderthal&lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:mvn/version&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;0.49.1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;;; NVIDIA GPU support&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nv"&gt;uncomplicate/clojurecuda&lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:mvn/version&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;0.19.0&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;;; Intel MKL CPU support&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nv"&gt;org.bytedeco/mkl-platform-redist&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:mvn/version&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;2024.0-1.5.10&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;}}}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Our clojure namespace requires the top-level APIs we'll be working with.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;ns &lt;/span&gt;&lt;span class="nv"&gt;linalg&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:require&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;uncomplicate.clojurecuda.core&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="ss"&gt;:as&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;cuda&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;uncomplicate.commons.core&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="ss"&gt;:refer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;with-release&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;uncomplicate.neanderthal.cuda&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="ss"&gt;:refer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;cuv&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;cuge&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;with-default-engine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;with-engine&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;uncomplicate.neanderthal.native&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:refer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;dv&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;dge&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;fge&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;uncomplicate.neanderthal.random&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:refer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;rand-uniform!&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;rand-normal!&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;uncomplicate.neanderthal.core&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="ss"&gt;:refer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;mm!&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;zero&lt;/span&gt;&lt;span class="p"&gt;]]))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Next, we create two &lt;code&gt;n x n&lt;/code&gt; matrices, X and Y, and do a matrix multiplication into a pre-allocated
&lt;code&gt;output&lt;/code&gt; matrix. Here, &lt;code&gt;dge&lt;/code&gt; stands for &lt;em&gt;Double General Matrix&lt;/em&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;def &lt;/span&gt;&lt;span class="nv"&gt;n&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;timed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;cpu&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;n&lt;/span&gt;
&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;let &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;X&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;rand-uniform!&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;dge&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;n&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;n&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="w"&gt;             &lt;/span&gt;&lt;span class="nv"&gt;Y&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;rand-uniform!&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;dge&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;n&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;n&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="w"&gt;             &lt;/span&gt;&lt;span class="nv"&gt;output&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;zero&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;X&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;timed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;cpu-multiply&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;n&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;mm!&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;X&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;Y&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;output&lt;/span&gt;&lt;span class="p"&gt;))))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;And the code for the GPU. The three differences&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Note the use of &lt;code&gt;cuge&lt;/code&gt; (&lt;em&gt;CUDA General Matrix&lt;/em&gt;) instead of &lt;code&gt;dge&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;We need to wrap the operation in a few CUDA-specific lines to set up the engine.&lt;/li&gt;
&lt;li&gt;Because GPU calls are asynchronous, we need to call &lt;code&gt;syncronize&lt;/code&gt; to make sure we capture the actual work.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;timed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;gpu&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;n&lt;/span&gt;
&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;cuda/with-default&lt;/span&gt;
&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;with-default-engine&lt;/span&gt;
&lt;span class="w"&gt;           &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;with-release&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;X&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;rand-uniform!&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;cuge&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;n&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;n&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="w"&gt;                          &lt;/span&gt;&lt;span class="nv"&gt;Y&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;rand-uniform!&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;cuge&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;n&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;n&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="w"&gt;                          &lt;/span&gt;&lt;span class="nv"&gt;output&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;zero&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;X&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="w"&gt;             &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;timed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;gpu-multiply&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;n&lt;/span&gt;
&lt;span class="w"&gt;                   &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;do&lt;/span&gt;
&lt;span class="w"&gt;                     &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;mm!&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;X&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;Y&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;output&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;                     &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;cuda/synchronize!&lt;/span&gt;&lt;span class="p"&gt;)))))))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;timed&lt;/code&gt; call here is a customized version of the built-in Clojure &lt;code&gt;time&lt;/code&gt; macro. 
As clojure macros get a bit off topic, the details aren't important.
I'll just leave the code here for anyone interested. &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;defmacro &lt;/span&gt;&lt;span class="nv"&gt;timed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;metadata&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;expr&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;let &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;sym&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;= &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;expr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;clojure.lang.Symbol&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;`&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;let &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;start#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;. &lt;/span&gt;&lt;span class="nv"&gt;System&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;nanoTime&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="w"&gt;           &lt;/span&gt;&lt;span class="nv"&gt;return#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;~&lt;/span&gt;&lt;span class="nv"&gt;expr&lt;/span&gt;
&lt;span class="w"&gt;           &lt;/span&gt;&lt;span class="nv"&gt;res#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="o"&gt;~&lt;/span&gt;&lt;span class="nv"&gt;sym&lt;/span&gt;
&lt;span class="w"&gt;                  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;resolve &lt;/span&gt;&lt;span class="o"&gt;&amp;#39;~&lt;/span&gt;&lt;span class="nv"&gt;expr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;                  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;resolve &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;first &lt;/span&gt;&lt;span class="o"&gt;&amp;#39;~&lt;/span&gt;&lt;span class="nv"&gt;expr&lt;/span&gt;&lt;span class="p"&gt;)))]&lt;/span&gt;
&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;prn &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;str&lt;/span&gt;
&lt;span class="w"&gt;             &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;clojure.string/join&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;,&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;~&lt;/span&gt;&lt;span class="nv"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;             &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;,&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;             &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;/ &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;double &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;- &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;. &lt;/span&gt;&lt;span class="nv"&gt;System&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;nanoTime&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;start#&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1000000.0&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="nv"&gt;return#&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;TLDR: it allows us to instrument our code and test it at various input sizes.&lt;/p&gt;
&lt;p&gt;The macro outputs comma-separated lines like this to stdout.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;gpu-mulitiply,8194,14.245
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;When run on both GPU and CPU for a variety of matrix sizes then plotted with a log-y axis, we can see several clear patterns. Note the y-axis is log scale.&lt;/p&gt;
&lt;p&gt;&lt;img src="images/matrix-mult.png"&gt;&lt;/p&gt;
&lt;p&gt;Some takeaways:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Below a certain size threshold, the CPU wins hands down. GPU is busy initializing, note the gap between the multiplication and the overall time. All of that time is effectively a sunk cost.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;There is a narrow band around the threshold where the CPU and GPU are roughly equal. But the advantage changes quickly.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Above the threshold, the GPU is orders of magnitude faster. It's not even in the same ballpark; the speed difference is so great that it could improve processes that currently take days down to seconds. That's not just performance optimization, that's completely game changing.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;So, Answer 1: GPU's advantage is highly sensitive to the amount of data you can feed it. If you can't keep it busy or rely on it for tasks that it really can't do well, you're wasting resources initializing GPU memory.&lt;/p&gt;
&lt;p&gt;&lt;img src="images/sea-the-ship-a-container-ship-tug-wallpaper-preview.jpg"&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Asking a GPU to crunch a few MBs of data is like asking a container ship to mail a letter.&lt;/em&gt; &lt;/p&gt;
&lt;h2&gt;The rest of the process&lt;/h2&gt;
&lt;p&gt;Let's assume your number crunching code is proven to be a good match for GPU work.&lt;/p&gt;
&lt;p&gt;Most code will still need to run in the context of a larger process. At the very least, &lt;em&gt;something&lt;/em&gt; has to load the data into memory and onto the GPU - off of disk or network or other input streams. Then it presumably takes the result from the GPU and does the reverse - writes to disk or pushes it to the network or output streams. We are not allowed to cheat and pretend that these don't count as part of the overall cost.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Amdahl's_law"&gt;Amdahl's Law&lt;/a&gt; provides a good description of the problem:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;"the overall performance improvement gained by optimizing a single part of a system is limited by the fraction of time that the improved part is actually used"&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src="images/Optimizing-different-parts.svg"&gt;&lt;/p&gt;
&lt;p&gt;So Answer 2: GPU optimization is only worth it if the relative gains make the &lt;em&gt;overall&lt;/em&gt; process significantly faster. 
No cheating by ignoring the non-parallelized parts.&lt;/p&gt;
&lt;h2&gt;Microeconomics of matrix math&lt;/h2&gt;
&lt;p&gt;Even if our numeric code is faster on the GPU, and even if it does speed up the process overall to make it "worth it" from a clock time perspective, how does that translate to cost?&lt;/p&gt;
&lt;p&gt;Let's start with what we know for sure. &lt;strong&gt;GPUs are significantly more expensive per hour&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;If your organization views data products on a cost-of-goods-sold basis, your increase in compute costs need to be offset by a decrease in processing time. A GPU may cost 20x more &lt;em&gt;per hour&lt;/em&gt; but if you can process the same volume of data 20x faster, you break even. If you process things 100x faster, you save money! If you process the data only 4x faster, your cost per unit went up 5x and you're not going to be happy.&lt;/p&gt;
&lt;p&gt;Or maybe cost per unit isn't relevant. Maybe you &lt;em&gt;are&lt;/em&gt; happy spending a premium to get faster speeds, or to do things that are practically impossible on a CPU. &lt;/p&gt;
&lt;p&gt;Either way, dollars need to enter the equation at this point.&lt;/p&gt;
&lt;p&gt;We can scale the time by the cost/time of the average ec2 and get a similar chart with a new y axis.
Note that the shape doesn't really changed but the threshold of "worth it" moves up.&lt;/p&gt;
&lt;p&gt;Answer 3: Depends on your willingness to pay for the decreased clock time. If it's speed at any cost, go for it! If you're constrained by efficiency, you need to normalize by the unit cost over time and adjust your threshold accordingly.&lt;/p&gt;
&lt;h2&gt;CUDAHell&lt;/h2&gt;
&lt;p&gt;So you've committed to writing code for the GPU, now comes the fun part - putting it into production.&lt;/p&gt;
&lt;p&gt;At the very least, software teams will need access to GPU-capable development environments,
CI testing environments, and servers to deploy. Making that part of your process, if it's not already,
is a heavy lift for two reasons: expensive &lt;strong&gt;hardware&lt;/strong&gt; and proprietary software &lt;strong&gt;drivers&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;The market is tough for GPU hardware. Since it's exposed to fields like AI, gaming and cryptocurrency, demand and prices can swing wildly.
Getting adequate developer laptops is a challenge.&lt;/p&gt;
&lt;p&gt;Then there's the software drivers. 
Proprietary GPU drivers are terrible; I don't think I need to elaborate. 
Installing CUDA or OpenCL on one machine is a challenge. 
Installing it consistently on a fleet of developer and production machines can be a full time job.&lt;/p&gt;
&lt;p&gt;The operational challenges of adding GPUs are significant. Those with unlimited AWS budgets and devops departments that take care of provisioning for you may not think of it as a big deal. But that's missing the point - proposing to add GPUs to your software stack is fundamentally an economic proposition, even if someone else is paying. Adding a GPU means adding complexity and costs and that needs to be justified.&lt;/p&gt;
&lt;p&gt;Answer 4: Run through the full software dev-test-release process in a staging environment to make sure you can live with the additional operational challenges. Incorporate those as costs, again bumping up your threshold.&lt;/p&gt;
&lt;h2&gt;Review&lt;/h2&gt;
&lt;p&gt;I've outlined a rough methodology to justify using a GPU (or not) in your work.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Write code that works on both GPU + CPU. I realize this is easier said than done!&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Run both on the domain of input sizes.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Find the performance threshold that makes it worth it for your process.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Find your economic threshold.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Adjust your developer experience, testing and release process.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;We can't just assume a GPU will be a silver bullet.
All 5 of the above require a commitment of resources from your organization,
with the realization that you may need to bail out if it does not pay off.
GPUs are risky, but the potential upsides are too compelling to ignore.&lt;/p&gt;</content><category term="articles"></category><category term="Clojure"></category><category term="GPU"></category><category term="Math"></category><category term="LinearAlgebra"></category></entry><entry><title>Getting started with application configuration in Rust</title><link href="https://www.perrygeo.com/getting-started-with-application-configuration-in-rust.html" rel="alternate"></link><published>2022-12-03T00:00:00-07:00</published><updated>2022-12-03T00:00:00-07:00</updated><author><name>Matthew T. Perry</name></author><id>tag:www.perrygeo.com,2022-12-03:/getting-started-with-application-configuration-in-rust.html</id><summary type="html">&lt;p&gt;Sooner or later, that application you're writing will need to be configured.
At the very least, you'll need a way to adjust inputs without editing source code. Wouldn't it be nice to have a reasonable configuration system from the start?&lt;/p&gt;
&lt;p&gt;The best way to configure your app will depend on …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Sooner or later, that application you're writing will need to be configured.
At the very least, you'll need a way to adjust inputs without editing source code. Wouldn't it be nice to have a reasonable configuration system from the start?&lt;/p&gt;
&lt;p&gt;The best way to configure your app will depend on
the environment in which you're using the software,
and the requirements of the project, all of which will change over time.
Ideally, we'd start out with a system
that had the flexibility to pull our configuration from a number of input sources:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Command Line Interface&lt;/strong&gt; for interactive development with standard flags, clear usage and error handling&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;.env&lt;/code&gt;&lt;/strong&gt; files for declarative configuration, either development or production&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Environment variables&lt;/strong&gt; for containers and many production settings&lt;/li&gt;
&lt;li&gt;Reasonable &lt;strong&gt;defaults&lt;/strong&gt; if nothing is provided by the user. And if there is no obvious default, mark it clearly as a mandatory argument.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For a language that is often refered to
as a low-level "systems" language, Rust allows for some very ergonomic
abstractions. We can implement a type-safe configuration system
with a minimal amount of imperative code, letting the third-party crates handle the mechanical details. Let's walk through a new project...&lt;/p&gt;
&lt;h2&gt;Project setup&lt;/h2&gt;
&lt;p&gt;In this example, we'll create a Rust project using the &lt;code&gt;clap&lt;/code&gt; and &lt;code&gt;dotenv&lt;/code&gt; crates.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;cargo&lt;span class="w"&gt; &lt;/span&gt;new&lt;span class="w"&gt; &lt;/span&gt;myapp
&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;myapp

cargo&lt;span class="w"&gt; &lt;/span&gt;add&lt;span class="w"&gt; &lt;/span&gt;clap&lt;span class="w"&gt; &lt;/span&gt;--features&lt;span class="w"&gt; &lt;/span&gt;derive,env
cargo&lt;span class="w"&gt; &lt;/span&gt;add&lt;span class="w"&gt; &lt;/span&gt;dotenv
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Your &lt;code&gt;Cargo.toml&lt;/code&gt; file should look something like&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;[dependencies]&lt;/span&gt;
&lt;span class="n"&gt;clap&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;4.0.29&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;features&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;derive&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;env&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;dotenv&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;0.15.0&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Creating the configuration struct&lt;/h2&gt;
&lt;p&gt;Let's build it up from scratch, starting with a plain &lt;code&gt;struct&lt;/code&gt; defining all values we need to configure the app.&lt;/p&gt;
&lt;p&gt;In our &lt;code&gt;src/main.rs&lt;/code&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;pub&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Config&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;pub&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ipaddr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;pub&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;i32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;pub&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;database_url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Let's pause for a second to consider types. In Rust, types can help us out by providing powerful correctness guarantees.&lt;/p&gt;
&lt;p&gt;Is &lt;code&gt;ipaddr&lt;/code&gt; really a String? The type system should enforce a valid IPv4 address instead of a free-form string.
Likewise, let's make sure the &lt;code&gt;port&lt;/code&gt; is a unsigned 16 bit integer to stay within
the range of viable port numbers.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;net&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Ipv4Addr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;pub&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Config&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;pub&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ipaddr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Ipv4Addr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;pub&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;u16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;pub&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;database_url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Clap annotations&lt;/h2&gt;
&lt;p&gt;Next, we use the &lt;a href="https://docs.rs/clap/latest/clap/index.html"&gt;&lt;code&gt;clap&lt;/code&gt;&lt;/a&gt; crate and add annotations to our struct.&lt;/p&gt;
&lt;p&gt;This turns our declarative struct into a powerful command line interface,
with error handling, default values and type conversion.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;clap&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Parser&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;net&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Ipv4Addr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="cp"&gt;#[derive(Parser, Debug)]&lt;/span&gt;
&lt;span class="cp"&gt;#[command(author, version, about)]&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Config&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cp"&gt;#[arg(short, long, default_value = &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;0.0.0.0&amp;quot;&lt;/span&gt;&lt;span class="cp"&gt;)]&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;pub&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ipaddr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Ipv4Addr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cp"&gt;#[arg(short, long, default_value_t = 3000)]&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;pub&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;u16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cp"&gt;#[arg(short, long)]&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;pub&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;database_url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The author, version and about text are derived from the contents of our &lt;code&gt;Cargo.toml&lt;/code&gt; file.&lt;/p&gt;
&lt;p&gt;Note that the &lt;code&gt;database_url&lt;/code&gt; does not use a default value.&lt;/p&gt;
&lt;h2&gt;Self documentation&lt;/h2&gt;
&lt;p&gt;We can add docstrings (&lt;code&gt;///&lt;/code&gt;) to the struct and to its members.
This serves the purpose of both documenting the code and exposing
friendly command line usage and error messages.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;clap&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Parser&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;net&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Ipv4Addr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="sd"&gt;/// My Awesome Application&lt;/span&gt;
&lt;span class="cp"&gt;#[derive(Parser, Debug)]&lt;/span&gt;
&lt;span class="cp"&gt;#[command(author, version, about)]&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Config&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;/// IPv4 address&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cp"&gt;#[arg(short, long, default_value = &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;0.0.0.0&amp;quot;&lt;/span&gt;&lt;span class="cp"&gt;)]&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;pub&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ipaddr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Ipv4Addr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;/// Port number&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cp"&gt;#[arg(short, long, default_value_t = 3000)]&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;pub&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;u16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;/// Database connection string&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cp"&gt;#[arg(short, long)]&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;pub&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;database_url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Environment handling&lt;/h2&gt;
&lt;p&gt;Clap can handle env vars explicitly by add the &lt;code&gt;env(...)&lt;/code&gt; annotation
to each configuration item. Here, we explictly define each variable name
using the &lt;code&gt;APP_*&lt;/code&gt; prefix, all upper case, as a convention:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;clap&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Parser&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;dotenv&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;dotenv&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;net&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Ipv4Addr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="sd"&gt;/// My Awesome Application&lt;/span&gt;
&lt;span class="cp"&gt;#[derive(Parser, Debug)]&lt;/span&gt;
&lt;span class="cp"&gt;#[command(author, version, about)]&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Config&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;/// IPv4 address&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cp"&gt;#[arg(short, long, env(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;APP_IPADDR&amp;quot;&lt;/span&gt;&lt;span class="cp"&gt;), default_value = &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;0.0.0.0&amp;quot;&lt;/span&gt;&lt;span class="cp"&gt;)]&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;pub&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ipaddr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Ipv4Addr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;/// Port number&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cp"&gt;#[arg(short, long, env(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;APP_PORT&amp;quot;&lt;/span&gt;&lt;span class="cp"&gt;), default_value_t = 3000)]&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;pub&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;u16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;/// Database connection string&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cp"&gt;#[arg(short, long, env(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;APP_DATABASE_URL&amp;quot;&lt;/span&gt;&lt;span class="cp"&gt;))]&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;pub&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;database_url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Constructor&lt;/h2&gt;
&lt;p&gt;Since we want to (optionally) populate our environment using a &lt;code&gt;.env&lt;/code&gt; file,
we have to set up the environment before invoking the Clap parser. To do this,
We'll implement a &lt;code&gt;from_env_and_args&lt;/code&gt; constructor method for our &lt;code&gt;Config&lt;/code&gt; struct.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;impl&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Config&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;pub&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;from_env_and_args&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Self&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;dotenv&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="bp"&gt;Self&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;With four potential inputs, how do we reason about which takes precendence?
To determine the config value, the effective order is as follows, &lt;em&gt;first one&lt;/em&gt; wins:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Command line interface argument&lt;/li&gt;
&lt;li&gt;File (&lt;code&gt;.env&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Environment variable&lt;/li&gt;
&lt;li&gt;Default value&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Main&lt;/h2&gt;
&lt;p&gt;Finally, we write our main function to create and construct the &lt;code&gt;Config&lt;/code&gt; at runtime.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cfg&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;from_env_and_args&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="fm"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Starting HTTP server on {}:{}&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cfg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ipaddr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cfg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="fm"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Connecting to {}&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cfg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;database_url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Presumably, your application will do something more interesting here!&lt;/p&gt;
&lt;h2&gt;Result&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;cargo&lt;span class="w"&gt; &lt;/span&gt;build
...
$&lt;span class="w"&gt; &lt;/span&gt;./target/debug/myapp&lt;span class="w"&gt; &lt;/span&gt;--help
My&lt;span class="w"&gt; &lt;/span&gt;Awesome&lt;span class="w"&gt; &lt;/span&gt;Application

Usage:&lt;span class="w"&gt; &lt;/span&gt;myapp&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;OPTIONS&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;--database-url&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;DATABASE_URL&amp;gt;

Options:
&lt;span class="w"&gt;  &lt;/span&gt;-i,&lt;span class="w"&gt; &lt;/span&gt;--ipaddr&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;IPADDR&amp;gt;&lt;span class="w"&gt;              &lt;/span&gt;IPv4&lt;span class="w"&gt; &lt;/span&gt;address&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;env:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;APP_IPADDR&lt;/span&gt;&lt;span class="o"&gt;=]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;default:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.0.0.0&lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;-p,&lt;span class="w"&gt; &lt;/span&gt;--port&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;PORT&amp;gt;&lt;span class="w"&gt;                  &lt;/span&gt;Port&lt;span class="w"&gt; &lt;/span&gt;number&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;env:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;APP_PORT&lt;/span&gt;&lt;span class="o"&gt;=]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;default:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3000&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;-d,&lt;span class="w"&gt; &lt;/span&gt;--database-url&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;DATABASE_URL&amp;gt;&lt;span class="w"&gt;  &lt;/span&gt;Database&lt;span class="w"&gt; &lt;/span&gt;connection&lt;span class="w"&gt; &lt;/span&gt;string&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;env:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;APP_DATABASE_URL&lt;/span&gt;&lt;span class="o"&gt;=]&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;-h,&lt;span class="w"&gt; &lt;/span&gt;--help&lt;span class="w"&gt;                         &lt;/span&gt;Print&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;help&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;information
&lt;span class="w"&gt;  &lt;/span&gt;-V,&lt;span class="w"&gt; &lt;/span&gt;--version&lt;span class="w"&gt;                      &lt;/span&gt;Print&lt;span class="w"&gt; &lt;/span&gt;version&lt;span class="w"&gt; &lt;/span&gt;information
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;In this case, we see that the &lt;code&gt;database_url&lt;/code&gt; is undefined in the environment, has no default, but
is required by the application. If we try to run it now, the app exits with status
code of &lt;code&gt;2&lt;/code&gt; and we get a human-readable message that we are missing the database URL:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;./target/debug/myapp

error:&lt;span class="w"&gt; &lt;/span&gt;The&lt;span class="w"&gt; &lt;/span&gt;following&lt;span class="w"&gt; &lt;/span&gt;required&lt;span class="w"&gt; &lt;/span&gt;arguments&lt;span class="w"&gt; &lt;/span&gt;were&lt;span class="w"&gt; &lt;/span&gt;not&lt;span class="w"&gt; &lt;/span&gt;provided:
&lt;span class="w"&gt;  &lt;/span&gt;--database-url&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;DATABASE_URL&amp;gt;

Usage:&lt;span class="w"&gt; &lt;/span&gt;myapp&lt;span class="w"&gt; &lt;/span&gt;--database-url&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;DATABASE_URL&amp;gt;

For&lt;span class="w"&gt; &lt;/span&gt;more&lt;span class="w"&gt; &lt;/span&gt;information&lt;span class="w"&gt; &lt;/span&gt;try&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;--help&amp;#39;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;To provide it we have three options, depending on your operational needs.&lt;/p&gt;
&lt;p&gt;First, we can use the command line for interactive testing:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;./target/debug/myapp&lt;span class="w"&gt; &lt;/span&gt;--database-url&lt;span class="w"&gt; &lt;/span&gt;postgres://postgres@localhost:5432/postgres
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Or, an environment variable for production settings:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nb"&gt;export&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;APP_DATABASE_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;postgres://postgres@localhost:5432/postgres&amp;quot;&lt;/span&gt;
./target/debug/myapp
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Or finally, using a &lt;code&gt;.env&lt;/code&gt; file for declarative environment setup (in prod or dev).&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;APP_DATABASE_URL=postgres://postgres@localhost:5432/postgres&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;.env
./target/debug/myapp
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Whichever way we configure the required &lt;code&gt;DATABASE_URL&lt;/code&gt;, we get the same result.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;./target/debug/myapp
Starting&lt;span class="w"&gt; &lt;/span&gt;HTTP&lt;span class="w"&gt; &lt;/span&gt;server&lt;span class="w"&gt; &lt;/span&gt;on&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.0.0.0:3000
Connecting&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;postgres://postgres@localhost:5432/postgres
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Error handling is intuitive from the command line.
Let's see what happens when we provide an invalid IP adress and port number.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;./target/debug/myapp&lt;span class="w"&gt; &lt;/span&gt;--ipaddr&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;255&lt;/span&gt;.255.255.999
error:&lt;span class="w"&gt; &lt;/span&gt;Invalid&lt;span class="w"&gt; &lt;/span&gt;value&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;255.255.255.999&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;--ipaddr &amp;lt;IPADDR&amp;gt;&amp;#39;&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;invalid&lt;span class="w"&gt; &lt;/span&gt;IPv4&lt;span class="w"&gt; &lt;/span&gt;address&lt;span class="w"&gt; &lt;/span&gt;syntax

For&lt;span class="w"&gt; &lt;/span&gt;more&lt;span class="w"&gt; &lt;/span&gt;information&lt;span class="w"&gt; &lt;/span&gt;try&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;--help&amp;#39;&lt;/span&gt;

$&lt;span class="w"&gt; &lt;/span&gt;./target/debug/myapp&lt;span class="w"&gt; &lt;/span&gt;--port&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;999999&lt;/span&gt;
error:&lt;span class="w"&gt; &lt;/span&gt;Invalid&lt;span class="w"&gt; &lt;/span&gt;value&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;999999&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;--port &amp;lt;PORT&amp;gt;&amp;#39;&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;999999&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;is&lt;span class="w"&gt; &lt;/span&gt;not&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;..&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;65535&lt;/span&gt;

For&lt;span class="w"&gt; &lt;/span&gt;more&lt;span class="w"&gt; &lt;/span&gt;information&lt;span class="w"&gt; &lt;/span&gt;try&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;--help&amp;#39;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Viola. A simple, declarative, type-safe abstraction with minimal code.
We get operational flexibility and confidence in the validity of the inputs
without writing imperative code to handle the details of each scenario.&lt;/p&gt;
&lt;p&gt;This can serve as a starter template suitable for most backend server or command line applications. Here it is, all 26 lines of code in one place:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;clap&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Parser&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;dotenv&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;dotenv&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;net&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Ipv4Addr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="sd"&gt;/// My Awesome Application&lt;/span&gt;
&lt;span class="cp"&gt;#[derive(Parser, Debug)]&lt;/span&gt;
&lt;span class="cp"&gt;#[command(author, version, about)]&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Config&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;/// IPv4 address&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cp"&gt;#[arg(short, long, env(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;APP_IPADDR&amp;quot;&lt;/span&gt;&lt;span class="cp"&gt;), default_value = &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;0.0.0.0&amp;quot;&lt;/span&gt;&lt;span class="cp"&gt;)]&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;pub&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ipaddr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Ipv4Addr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;/// Port number&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cp"&gt;#[arg(short, long, env(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;APP_PORT&amp;quot;&lt;/span&gt;&lt;span class="cp"&gt;), default_value_t = 3000)]&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;pub&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;u16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;/// Database connection string&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cp"&gt;#[arg(short, long, env(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;APP_DATABASE_URL&amp;quot;&lt;/span&gt;&lt;span class="cp"&gt;))]&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;pub&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;database_url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;impl&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Config&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;pub&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;from_env_and_args&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Self&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;dotenv&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="bp"&gt;Self&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cfg&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;from_env_and_args&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="fm"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Starting HTTP server on {}:{}&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cfg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ipaddr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cfg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="fm"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Connecting to {}&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cfg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;database_url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Check out the &lt;a href="https://docs.rs/clap/latest/clap/index.html"&gt;clap docs&lt;/a&gt; for more examples
of how you can extend this approach.&lt;/p&gt;
&lt;p&gt;I think this interface shows that we don't need to compromise between ergonomics and type-safety, speed and correctness. It's a great example of Rust's potential as a higher level application language.&lt;/p&gt;</content><category term="articles"></category><category term="rust"></category><category term="cli"></category><category term="clap"></category><category term="configuration"></category></entry><entry><title>Don't install PostgreSQL - Using containers for local development.</title><link href="https://www.perrygeo.com/dont-install-postgresql-using-containers-for-local-development.html" rel="alternate"></link><published>2022-02-11T00:00:00-07:00</published><updated>2022-02-11T00:00:00-07:00</updated><author><name>Matthew T. Perry</name></author><id>tag:www.perrygeo.com,2022-02-11:/dont-install-postgresql-using-containers-for-local-development.html</id><summary type="html">&lt;p&gt;So you need a database for an application you're developing. You've looked around and decided that PostgreSQL fits the bill. Excellent choice! Now it's time to start coding. How do you get postgres running locally to devlop and test against it?&lt;/p&gt;
&lt;p&gt;The typical suggestion for many web application frameworks is …&lt;/p&gt;</summary><content type="html">&lt;p&gt;So you need a database for an application you're developing. You've looked around and decided that PostgreSQL fits the bill. Excellent choice! Now it's time to start coding. How do you get postgres running locally to devlop and test against it?&lt;/p&gt;
&lt;p&gt;The typical suggestion for many web application frameworks is to install PostgreSQL to your system using your chosen dependency management tool - &lt;code&gt;brew install postgresql&lt;/code&gt; or &lt;code&gt;apt install postgresql&lt;/code&gt; - then configure it to work for your application (maybe tweaking some settings in &lt;code&gt;/etc/postgresql/&lt;/code&gt; as the root user), starting a background process with your system supervisor of choice (&lt;code&gt;sudo systemctl start postgresql&lt;/code&gt;), hooking it up to your app, and you're off to the races.&lt;/p&gt;
&lt;p&gt;But what happens when you're working on project that needs a different major version of postgresql, with different extensions or entirely different settings? I often found myself in a scenario where my system was full of cruft, having been reworked many times over to swap out different postgresql instances. Additionally there is only a single data directory (&lt;code&gt;/etc/postgresql/&amp;lt;version&amp;gt;/main&lt;/code&gt;) so if you need the data to persist for more than a single project, you have to manage backup and restore each time you switch contexts.&lt;/p&gt;
&lt;p&gt;A traditional system install just doesn't cut it. We need a way to run many different postgres instances, independent of each other with isolated data, settings and software versions. We can use Docker containers to run postgresql in a more flexible way that allows for greater experimentation, data stability, and greatly improved ease of use.&lt;/p&gt;
&lt;h2&gt;Running postgres in Docker, the naive approach&lt;/h2&gt;
&lt;p&gt;There's no real secret to running Docker containers. We know that &lt;a href="https://hub.docker.com/_/postgres/"&gt;postgresql docker images&lt;/a&gt; exist and we should be able to run them like any other.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;docker&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;postgres&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;14.1&lt;/span&gt;
&lt;span class="n"&gt;Unable&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;find&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;postgres:14.1&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;locally&lt;/span&gt;
&lt;span class="mf"&gt;14.1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Pulling&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;library&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;postgres&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="n"&gt;Status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Downloaded&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;newer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;postgres&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;14.1&lt;/span&gt;
&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Database&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;uninitialized&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;and&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;superuser&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;not&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;specified&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="n"&gt;You&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;must&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;specify&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;POSTGRES_PASSWORD&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;non&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;empty&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;the&lt;/span&gt;
&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="n"&gt;superuser&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;For&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;example&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;-e POSTGRES_PASSWORD=password&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;on&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;docker run&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;

&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="n"&gt;You&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;may&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;also&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;use&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;POSTGRES_HOST_AUTH_METHOD=trust&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;allow&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;all&lt;/span&gt;
&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="n"&gt;connections&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;without&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;This&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="ow"&gt;not&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;recommended&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;

&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="n"&gt;See&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;PostgreSQL&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;documentation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;about&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;trust&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="n"&gt;https&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="n"&gt;www&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;postgresql&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;org&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;docs&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;trust&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;html&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Ah, clearly there are a few tricks specific to running postgres in a container. If we set a postgres password, we can get a running postgres instance.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;docker&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;-e&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;POSTGRES_PASSWORD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;password&lt;span class="w"&gt; &lt;/span&gt;postgres:14.1
...
&lt;span class="m"&gt;2022&lt;/span&gt;-02-03&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;18&lt;/span&gt;:23:38.823&lt;span class="w"&gt; &lt;/span&gt;UTC&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;LOG:&lt;span class="w"&gt;  &lt;/span&gt;database&lt;span class="w"&gt; &lt;/span&gt;system&lt;span class="w"&gt; &lt;/span&gt;is&lt;span class="w"&gt; &lt;/span&gt;ready&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;accept&lt;span class="w"&gt; &lt;/span&gt;connections
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The container startup script will initialize your database, create users and start the process, listening for connections. But where is it listening? We can't yet connect to it. And where is the data? We can't see any data anywhere on our host system. Everything is, well, contained within the running Docker container.&lt;/p&gt;
&lt;p&gt;To make this workflow viable for local development, we'd like&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;An open TCP port on the host system so we can connect to it.&lt;/li&gt;
&lt;li&gt;The data to live on the host system, not in the container's overlay filesystem.&lt;/li&gt;
&lt;li&gt;To give postgres access to files from the host system so that we can import datasets.&lt;/li&gt;
&lt;li&gt;Settings to live on the host system so that we can adjust them and optionally check them into source control.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Of course the offical &lt;a href="https://hub.docker.com/_/postgres/"&gt;PostgreSQL Docker documentation&lt;/a&gt; covers these exact scenarios, showing us how we can use &lt;em&gt;port forwarding&lt;/em&gt; and &lt;em&gt;volume mounts&lt;/em&gt;.&lt;/p&gt;
&lt;h1&gt;An alternative to system-wide PostgreSQL installs&lt;/h1&gt;
&lt;p&gt;Here is my opinionated take on how to set up an ergonomic postgres environment for local development.&lt;/p&gt;
&lt;p&gt;First, create a &lt;code&gt;database&lt;/code&gt; directory in your project to hold all things postgres&lt;/p&gt;
&lt;p&gt;Then create &lt;code&gt;database/postgresql.conf&lt;/code&gt; to specify the postgres settings. The example below is a subset of the full postgres config, the settings that I typically need to adjust when doing any serious performance-sensistive development&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c"&gt;# PostgreSQL configuration file&lt;/span&gt;
&lt;span class="c"&gt;# See https://github&lt;/span&gt;&lt;span class="nt"&gt;.&lt;/span&gt;&lt;span class="c"&gt;com/postgres/postgres/blob/master/src/backend/utils/misc/postgresql&lt;/span&gt;&lt;span class="nt"&gt;.&lt;/span&gt;&lt;span class="c"&gt;conf&lt;/span&gt;&lt;span class="nt"&gt;.&lt;/span&gt;&lt;span class="c"&gt;sample&lt;/span&gt;

&lt;span class="c"&gt;#&lt;/span&gt;&lt;span class="nb"&gt;------------------------------------------------------------------------------&lt;/span&gt;
&lt;span class="c"&gt;# CONNECTIONS AND AUTHENTICATION&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;&lt;span class="nb"&gt;------------------------------------------------------------------------------&lt;/span&gt;
&lt;span class="c"&gt;listen_addresses = &amp;#39;*&amp;#39;&lt;/span&gt;
&lt;span class="c"&gt;port = 5432             # (change requires restart)&lt;/span&gt;
&lt;span class="c"&gt;max_connections = 100           # (change requires restart)&lt;/span&gt;

&lt;span class="c"&gt;#&lt;/span&gt;&lt;span class="nb"&gt;------------------------------------------------------------------------------&lt;/span&gt;
&lt;span class="c"&gt;# RESOURCE USAGE (except WAL)&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;&lt;span class="nb"&gt;------------------------------------------------------------------------------&lt;/span&gt;
&lt;span class="c"&gt;shared_buffers = 2048MB         # min 128kB&lt;/span&gt;
&lt;span class="c"&gt;work_mem = 40MB             # min 64kB&lt;/span&gt;
&lt;span class="c"&gt;maintenance_work_mem = 640MB        # min 1MB&lt;/span&gt;
&lt;span class="c"&gt;dynamic_shared_memory_type = posix  # the default is the first option&lt;/span&gt;
&lt;span class="c"&gt;max_parallel_workers_per_gather = 6 # taken from max_parallel_workers&lt;/span&gt;
&lt;span class="c"&gt;max_parallel_workers = 12       # maximum number of max_worker_processes that&lt;/span&gt;

&lt;span class="c"&gt;#&lt;/span&gt;&lt;span class="nb"&gt;------------------------------------------------------------------------------&lt;/span&gt;
&lt;span class="c"&gt;# WRITE&lt;/span&gt;&lt;span class="nb"&gt;-&lt;/span&gt;&lt;span class="c"&gt;AHEAD LOG&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;&lt;span class="nb"&gt;------------------------------------------------------------------------------&lt;/span&gt;
&lt;span class="c"&gt;checkpoint_timeout = 40min      # range 30s&lt;/span&gt;&lt;span class="nb"&gt;-&lt;/span&gt;&lt;span class="c"&gt;1d&lt;/span&gt;
&lt;span class="c"&gt;max_wal_size = 1GB&lt;/span&gt;
&lt;span class="c"&gt;min_wal_size = 80MB&lt;/span&gt;
&lt;span class="c"&gt;checkpoint_completion_target = 0&lt;/span&gt;&lt;span class="nt"&gt;.&lt;/span&gt;&lt;span class="c"&gt;75 # checkpoint target duration&lt;/span&gt;&lt;span class="nt"&gt;,&lt;/span&gt;&lt;span class="c"&gt; 0&lt;/span&gt;&lt;span class="nt"&gt;.&lt;/span&gt;&lt;span class="c"&gt;0 &lt;/span&gt;&lt;span class="nb"&gt;-&lt;/span&gt;&lt;span class="c"&gt; 1&lt;/span&gt;&lt;span class="nt"&gt;.&lt;/span&gt;&lt;span class="c"&gt;0&lt;/span&gt;

&lt;span class="c"&gt;#&lt;/span&gt;&lt;span class="nb"&gt;------------------------------------------------------------------------------&lt;/span&gt;
&lt;span class="c"&gt;# REPORTING AND LOGGING&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;&lt;span class="nb"&gt;------------------------------------------------------------------------------&lt;/span&gt;
&lt;span class="c"&gt;logging_collector = off&lt;/span&gt;
&lt;span class="c"&gt;log_autovacuum_min_duration = 0&lt;/span&gt;
&lt;span class="c"&gt;log_checkpoints = on&lt;/span&gt;
&lt;span class="c"&gt;log_connections = on&lt;/span&gt;
&lt;span class="c"&gt;log_disconnections = on&lt;/span&gt;
&lt;span class="c"&gt;log_error_verbosity = default&lt;/span&gt;
&lt;span class="c"&gt;log_min_duration_statement = 20ms&lt;/span&gt;
&lt;span class="c"&gt;log_lock_waits = on&lt;/span&gt;
&lt;span class="c"&gt;log_temp_files = 0&lt;/span&gt;
&lt;span class="c"&gt;log_timezone = &amp;#39;UTC&amp;#39;&lt;/span&gt;

&lt;span class="c"&gt;#&lt;/span&gt;&lt;span class="nb"&gt;------------------------------------------------------------------------------&lt;/span&gt;
&lt;span class="c"&gt;# AUTOVACUUM&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;&lt;span class="nb"&gt;------------------------------------------------------------------------------&lt;/span&gt;
&lt;span class="c"&gt;autovacuum_vacuum_scale_factor = 0&lt;/span&gt;&lt;span class="nt"&gt;.&lt;/span&gt;&lt;span class="c"&gt;02   # fraction of table size before vacuum&lt;/span&gt;
&lt;span class="c"&gt;autovacuum_analyze_scale_factor = 0&lt;/span&gt;&lt;span class="nt"&gt;.&lt;/span&gt;&lt;span class="c"&gt;01  # fraction of table size before analyze&lt;/span&gt;

&lt;span class="c"&gt;#&lt;/span&gt;&lt;span class="nb"&gt;------------------------------------------------------------------------------&lt;/span&gt;
&lt;span class="c"&gt;# CLIENT CONNECTION DEFAULTS&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;&lt;span class="nb"&gt;------------------------------------------------------------------------------&lt;/span&gt;
&lt;span class="c"&gt;datestyle = &amp;#39;iso&lt;/span&gt;&lt;span class="nt"&gt;,&lt;/span&gt;&lt;span class="c"&gt; mdy&amp;#39;&lt;/span&gt;
&lt;span class="c"&gt;timezone = &amp;#39;UTC&amp;#39;&lt;/span&gt;
&lt;span class="c"&gt;lc_messages = &amp;#39;C&lt;/span&gt;&lt;span class="nt"&gt;.&lt;/span&gt;&lt;span class="c"&gt;UTF&lt;/span&gt;&lt;span class="nb"&gt;-&lt;/span&gt;&lt;span class="c"&gt;8&amp;#39;&lt;/span&gt;
&lt;span class="c"&gt;lc_monetary = &amp;#39;C&lt;/span&gt;&lt;span class="nt"&gt;.&lt;/span&gt;&lt;span class="c"&gt;UTF&lt;/span&gt;&lt;span class="nb"&gt;-&lt;/span&gt;&lt;span class="c"&gt;8&amp;#39;&lt;/span&gt;
&lt;span class="c"&gt;lc_numeric = &amp;#39;C&lt;/span&gt;&lt;span class="nt"&gt;.&lt;/span&gt;&lt;span class="c"&gt;UTF&lt;/span&gt;&lt;span class="nb"&gt;-&lt;/span&gt;&lt;span class="c"&gt;8&amp;#39;&lt;/span&gt;
&lt;span class="c"&gt;lc_time = &amp;#39;C&lt;/span&gt;&lt;span class="nt"&gt;.&lt;/span&gt;&lt;span class="c"&gt;UTF&lt;/span&gt;&lt;span class="nb"&gt;-&lt;/span&gt;&lt;span class="c"&gt;8&amp;#39;&lt;/span&gt;
&lt;span class="c"&gt;default_text_search_config = &amp;#39;pg_catalog&lt;/span&gt;&lt;span class="nt"&gt;.&lt;/span&gt;&lt;span class="c"&gt;english&amp;#39;&lt;/span&gt;
&lt;span class="c"&gt;shared_preload_libraries = &amp;#39;pg_stat_statements&amp;#39;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Create a &lt;code&gt;database/pg_hba.conf&lt;/code&gt; to control access to the database. You might need to adjust this to experiment with different networking setups, different users, etc. Usually the defaults here are fine.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;#&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;PostgreSQL&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;Client&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;Authentication&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;Configuration&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;File&lt;/span&gt;
#&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;===================================================&lt;/span&gt;
#&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;TYPE&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nv"&gt;DATABASE&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;USER&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nv"&gt;CIDR&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nv"&gt;ADDRESS&lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="nv"&gt;METHOD&lt;/span&gt;

#&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;Database&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;administrative&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;login&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;by&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;UNIX&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;sockets&lt;/span&gt;
#&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;local&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;Unix&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;domain&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;socket&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;connections&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;only&lt;/span&gt;
&lt;span class="nv"&gt;local&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="nv"&gt;all&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="nv"&gt;postgres&lt;/span&gt;&lt;span class="w"&gt;                          &lt;/span&gt;&lt;span class="nv"&gt;ident&lt;/span&gt;
&lt;span class="nv"&gt;local&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="nv"&gt;all&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="nv"&gt;all&lt;/span&gt;&lt;span class="w"&gt;                               &lt;/span&gt;&lt;span class="nv"&gt;ident&lt;/span&gt;

#&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;IPv4&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;connections&lt;/span&gt;:
&lt;span class="nv"&gt;host&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;all&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="nv"&gt;all&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="mi"&gt;172&lt;/span&gt;.&lt;span class="mi"&gt;17&lt;/span&gt;.&lt;span class="mi"&gt;0&lt;/span&gt;.&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="nv"&gt;md5&lt;/span&gt;

#&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;IPv6&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;connections&lt;/span&gt;:
&lt;span class="nv"&gt;host&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;all&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="nv"&gt;all&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;::&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;128&lt;/span&gt;&lt;span class="w"&gt;               &lt;/span&gt;&lt;span class="nv"&gt;md5&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Make two subdirectories to hold the data: &lt;code&gt;database/mnt_data&lt;/code&gt; to hold data you intend to import/export and &lt;code&gt;database/pgdata&lt;/code&gt; to hold the actual database.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;mkdir&lt;span class="w"&gt; &lt;/span&gt;mnt_data
$&lt;span class="w"&gt; &lt;/span&gt;mkdir&lt;span class="w"&gt; &lt;/span&gt;pgdata
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;You probably don't want to check your datasets or database into source control. Create a &lt;code&gt;database/.gitignore&lt;/code&gt; to ignore them&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="gh"&gt;#&lt;/span&gt; .gitignore
pgdata
mnt_data
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Finally, create a &lt;code&gt;run-postgres.sh&lt;/code&gt; script to launch the docker container with everything hooked up.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# run-postgres.sh&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-e
&lt;span class="nv"&gt;HOST_PORT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;5432&lt;/span&gt;
&lt;span class="nv"&gt;NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;postgres-dev
&lt;span class="nv"&gt;DOCKER_REPO&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;postgres
&lt;span class="nv"&gt;TAG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;14&lt;/span&gt;.1

docker&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;--rm&lt;span class="w"&gt; &lt;/span&gt;--name&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$NAME&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;--volume&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;/pgdata:/var/lib/pgsql/data&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;--volume&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;/mnt_data:/mnt/data&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;--volume&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;/pg_hba.conf:/etc/postgresql/pg_hba.conf&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;--volume&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;/postgresql.conf:/etc/postgresql/postgresql.conf&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;-e&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;POSTGRES_PASSWORD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;password&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;-e&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;POSTGRES_USER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;postgres&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;-e&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;PGDATA&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/var/lib/pgsql/data/pgdata14&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;-e&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;POSTGRES_INITDB_ARGS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;--data-checksums --encoding=UTF8&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;-e&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;POSTGRES_DB&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;db&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;-p&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;HOST_PORT&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;:5432&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;DOCKER_REPO&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;:&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;TAG&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;postgres&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;-c&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;config_file=/etc/postgresql/postgresql.conf&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;-c&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;hba_file=/etc/postgresql/pg_hba.conf&amp;#39;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Note the &lt;code&gt;HOST_PORT&lt;/code&gt; variable. If you've already got another database running on 5432, this won't work. This is where you need to get a bit creative and tune the process to your needs. What I typically do is use port &lt;strong&gt;6&lt;/strong&gt;432 and increment by one for every project so they don't conflict. This allows to run all of your databases at the same time on one machine. The only downside is you need to remember which port maps to which database!&lt;/p&gt;
&lt;h2&gt;Running it&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;./run-postgres.sh
...
&lt;span class="m"&gt;2022&lt;/span&gt;-02-03&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;19&lt;/span&gt;:13:09.673&lt;span class="w"&gt; &lt;/span&gt;UTC&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;LOG:&lt;span class="w"&gt;  &lt;/span&gt;starting&lt;span class="w"&gt; &lt;/span&gt;PostgreSQL&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;14&lt;/span&gt;.1&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;Debian&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;14&lt;/span&gt;.1-1.pgdg110+1&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;on&lt;span class="w"&gt; &lt;/span&gt;x86_64-pc-linux-gnu,&lt;span class="w"&gt; &lt;/span&gt;compiled&lt;span class="w"&gt; &lt;/span&gt;by&lt;span class="w"&gt; &lt;/span&gt;gcc&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;Debian&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;.2.1-6&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;.2.1&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;20210110&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;64&lt;/span&gt;-bit
&lt;span class="m"&gt;2022&lt;/span&gt;-02-03&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;19&lt;/span&gt;:13:09.673&lt;span class="w"&gt; &lt;/span&gt;UTC&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;LOG:&lt;span class="w"&gt;  &lt;/span&gt;listening&lt;span class="w"&gt; &lt;/span&gt;on&lt;span class="w"&gt; &lt;/span&gt;IPv4&lt;span class="w"&gt; &lt;/span&gt;address&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;0.0.0.0&amp;quot;&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;port&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;5432&lt;/span&gt;
&lt;span class="m"&gt;2022&lt;/span&gt;-02-03&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;19&lt;/span&gt;:13:09.673&lt;span class="w"&gt; &lt;/span&gt;UTC&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;LOG:&lt;span class="w"&gt;  &lt;/span&gt;listening&lt;span class="w"&gt; &lt;/span&gt;on&lt;span class="w"&gt; &lt;/span&gt;IPv6&lt;span class="w"&gt; &lt;/span&gt;address&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;::&amp;quot;&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;port&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;5432&lt;/span&gt;
&lt;span class="m"&gt;2022&lt;/span&gt;-02-03&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;19&lt;/span&gt;:13:09.677&lt;span class="w"&gt; &lt;/span&gt;UTC&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;LOG:&lt;span class="w"&gt;  &lt;/span&gt;listening&lt;span class="w"&gt; &lt;/span&gt;on&lt;span class="w"&gt; &lt;/span&gt;Unix&lt;span class="w"&gt; &lt;/span&gt;socket&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/var/run/postgresql/.s.PGSQL.5432&amp;quot;&lt;/span&gt;
&lt;span class="m"&gt;2022&lt;/span&gt;-02-03&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;19&lt;/span&gt;:13:09.685&lt;span class="w"&gt; &lt;/span&gt;UTC&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="m"&gt;26&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;LOG:&lt;span class="w"&gt;  &lt;/span&gt;database&lt;span class="w"&gt; &lt;/span&gt;system&lt;span class="w"&gt; &lt;/span&gt;was&lt;span class="w"&gt; &lt;/span&gt;shut&lt;span class="w"&gt; &lt;/span&gt;down&lt;span class="w"&gt; &lt;/span&gt;at&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2021&lt;/span&gt;-11-13&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;21&lt;/span&gt;:34:06&lt;span class="w"&gt; &lt;/span&gt;UTC
&lt;span class="m"&gt;2022&lt;/span&gt;-02-03&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;19&lt;/span&gt;:13:09.700&lt;span class="w"&gt; &lt;/span&gt;UTC&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;LOG:&lt;span class="w"&gt;  &lt;/span&gt;database&lt;span class="w"&gt; &lt;/span&gt;system&lt;span class="w"&gt; &lt;/span&gt;is&lt;span class="w"&gt; &lt;/span&gt;ready&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;accept&lt;span class="w"&gt; &lt;/span&gt;connections
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Using this setup, the logs are sent directly to &lt;code&gt;stdout&lt;/code&gt; so you'll see everything in the terminal. The ports and paths in the logs are &lt;em&gt;inside&lt;/em&gt; the container, so don't get fooled trying to find them on your host system.&lt;/p&gt;
&lt;p&gt;To connect, we use the defined host port&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;psql&lt;span class="w"&gt; &lt;/span&gt;postgres://postgres:password@localhost:6432/postgres
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;You can put data in &lt;code&gt;mnt_data&lt;/code&gt; from the host system, which will be exposed to postgresql as the &lt;code&gt;/mnt/data&lt;/code&gt; directory inside the container. For example, load it with psql using &lt;code&gt;COPY data FROM '/mnt/data/my.csv' WITH CSV HEADER;&lt;/code&gt;. Likewise, any data dumps or exports &lt;em&gt;from&lt;/em&gt; postgres can be output to this directory, immediately accessible to the host system.&lt;/p&gt;
&lt;p&gt;To stop the server, use Ctrl-C. The data will persist to your &lt;code&gt;pgdata&lt;/code&gt; directory. Resist the temptation to touch any files therein as they are managemed internally to postgres. But you can move the directory as a whole around the filesystem or to another machine. It's not quite as convenient as a process-less, single file SQLite database but it's close.&lt;/p&gt;
&lt;p&gt;Because the &lt;code&gt;pgdata&lt;/code&gt; directory is created by postgres which provides strong gaurantees that the on-disk
data format will be consistent within a major version, we can even use a different image altogether to access the same underlying dataset. This can be very handy for e.g. switching between vanilla postgres and postgis,
or for testing different versions of extensions, etc. As long as the image follows the basic rules of the postgres container behavior and uses the same major version, it should just work.&lt;/p&gt;
&lt;h2&gt;What about in production?&lt;/h2&gt;
&lt;p&gt;Installing postgresql on a VM or bare-metal server is still viable, especially if automated with configuration tools like Ansible or Chef. But there are other options.&lt;/p&gt;
&lt;p&gt;If your project is all-in on containers in production, consider checking out some of the Kubernetes operators for postgres.
You can use the exact same container image in production that you test on locally,
albeit with some additional operational concerns around availability
and stateful data. Operator software like
&lt;a href="https://www.crunchydata.com/products/crunchy-postgresql-for-kubernetes/"&gt;Crunchy PostgreSQL for Kubernetes&lt;/a&gt; and &lt;a href="https://www.kubegres.io"&gt;Kubegres&lt;/a&gt; can be configured for load balancing, high-availability, backups, monitoring, etc. which can ease the operational burden should your database require such things.&lt;/p&gt;
&lt;p&gt;Of course, there is always the cloud hosted option. I've used postgresql on both GCP Cloud SQL and AWS RDS and, while you give up some control of the environment and are no longer able to run the exact same database locally as you do in prod, the easy of adminstering these hosted databases might be worth it.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Docker containers provide a robust way to run postgres in local development, with very few compromises. A container-based workflow makes it easier to maintain multiple parallel database, and to move data freely between systems. For my money,
there's no need to &lt;code&gt;apt install&lt;/code&gt; postgres again.&lt;/p&gt;</content><category term="articles"></category><category term="postgresql"></category><category term="postgis"></category><category term="SQL"></category><category term="docker"></category></entry><entry><title>Zonal Stats with PostGIS Rasters, part 2</title><link href="https://www.perrygeo.com/zonal-stats-with-postgis-rasters-part-2.html" rel="alternate"></link><published>2020-11-28T00:00:00-07:00</published><updated>2020-11-28T00:00:00-07:00</updated><author><name>Matthew T. Perry</name></author><id>tag:www.perrygeo.com,2020-11-28:/zonal-stats-with-postgis-rasters-part-2.html</id><summary type="html">&lt;p&gt;In my &lt;a href="https://www.perrygeo.com/zonal-stats-with-postgis-rasters.html"&gt;last post&lt;/a&gt; I compared two approaches for calculating zonal statistics:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A Python approach using the rasterstats library&lt;/li&gt;
&lt;li&gt;A SQL approach using PostGIS rasters.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I came away happy that I could express zonal stats in SQL, but wasn't happy with the performance; an 87x slowdown compared to the equivalent …&lt;/p&gt;</summary><content type="html">&lt;p&gt;In my &lt;a href="https://www.perrygeo.com/zonal-stats-with-postgis-rasters.html"&gt;last post&lt;/a&gt; I compared two approaches for calculating zonal statistics:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A Python approach using the rasterstats library&lt;/li&gt;
&lt;li&gt;A SQL approach using PostGIS rasters.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I came away happy that I could express zonal stats in SQL, but wasn't happy with the performance; an 87x slowdown compared to the equivalent Python code. When in doubt though, it's user error! I received some good suggestions from readers of this blog (Thanks Stefan Jäger and Pierre Racine!) who suggested some performance enhancements from &lt;strong&gt;tiling&lt;/strong&gt; and &lt;strong&gt;spatial indexes&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Additionally, I wasn't happy with the setup of the last experiment; while PostGIS and Rasterio both interact with the underlying GDAL C API,
in my experiment they were using GDAL libraries of different origins. And I'm skeptical that my synthetic vector data was representive of all workloads. A common case for zonal statistics is aggregating a raster by (non-overlapping) administrative boundaries. The nature of the datasets can have a significant impact; best to go with something more realistic.&lt;/p&gt;
&lt;p&gt;Time for a reboot...&lt;/p&gt;
&lt;h2&gt;Reproducible containers&lt;/h2&gt;
&lt;p&gt;I used my &lt;a href="https://github.com/perrygeo/docker-postgres"&gt;&lt;code&gt;docker-postgres&lt;/code&gt; image&lt;/a&gt; to easily recreate an environment where everything is built from source against the same shared libraries.&lt;/p&gt;
&lt;p&gt;To run a postgresql server from a docker container (no messy install required) with
 local data volumes mounted in &lt;code&gt;./pgdata&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;git&lt;span class="w"&gt; &lt;/span&gt;clone&lt;span class="w"&gt; &lt;/span&gt;https://github.com/perrygeo/docker-postgres.git
&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;docker-postgres
./run-postgres.sh
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This will download a pre-built image &lt;a href="https://hub.docker.com/r/perrygeo/postgres/tags?page=1&amp;amp;ordering=last_updated"&gt;from Dockerhub&lt;/a&gt; so you can try it out without messing with your system. Then launches
the Postgresql server process, with your local &lt;code&gt;pgdata&lt;/code&gt;, &lt;code&gt;mnt_data&lt;/code&gt; and &lt;code&gt;log&lt;/code&gt; directories mounted as container
volumes. &lt;/p&gt;
&lt;p&gt;In order to run Python code from the same container, we can &lt;code&gt;exec&lt;/code&gt; into it to get shell access:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;docker&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;exec&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-ti&lt;span class="w"&gt; &lt;/span&gt;postgres-server&lt;span class="w"&gt; &lt;/span&gt;/bin/bash
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;From here we can run our Python-based command line tools (Rasterio)&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;rio&lt;span class="w"&gt; &lt;/span&gt;--version
&lt;span class="m"&gt;1&lt;/span&gt;.1.8
$&lt;span class="w"&gt; &lt;/span&gt;rio&lt;span class="w"&gt; &lt;/span&gt;--gdal-version
&lt;span class="m"&gt;3&lt;/span&gt;.2.0
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Connecting to the server with &lt;code&gt;psql&lt;/code&gt;, I can use the built-in version commands to show what we're working with&lt;/p&gt;
&lt;p&gt;&lt;code&gt;SELECT version();&lt;/code&gt; &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;PostgreSQL 13.0 on x86_64-pc-linux-gnu, compiled by gcc (Debian 8.3.0-6) 8.3.0, 64-bit
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;SELECT postgis_full_version();&lt;/code&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;POSTGIS=&amp;quot;3.1.0alpha3 b2221ee&amp;quot;
PGSQL=&amp;quot;130&amp;quot;
GEOS=&amp;quot;3.9.0dev-CAPI-1.14.0&amp;quot;
PROJ=&amp;quot;7.2.0&amp;quot;
GDAL=&amp;quot;GDAL 3.2.0, released 2020/10/26&amp;quot;
LIBXML=&amp;quot;2.9.4&amp;quot;
LIBJSON=&amp;quot;0.12.1&amp;quot;
LIBPROTOBUF=&amp;quot;1.3.3&amp;quot;
WAGYU=&amp;quot;0.5.0 (Internal)&amp;quot;
RASTER
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Since the Rasterio library is running in the container, linked to exact same GDAL, GEOS and PROJ libraries as PostGIS, we can be assured of a more consistent environment.&lt;/p&gt;
&lt;h2&gt;Raster dataset&lt;/h2&gt;
&lt;p&gt;For our raster dataset, we'll use the historic climate data provided by the &lt;a href="https://worldclim.org/data/worldclim21.html"&gt;WorldClim&lt;/a&gt; project. For our experiment we'll use the historic average monthly temperature rasters.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;wget&lt;span class="w"&gt; &lt;/span&gt;http://biogeo.ucdavis.edu/data/worldclim/v2.1/base/wc2.1_2.5m_tavg.zip
unzip&lt;span class="w"&gt; &lt;/span&gt;wc2.1_2.5m_tavg.zip
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The result is a dozen monthly GeoTIFF files representing the historic average temperature for the month - we'll use &lt;code&gt;wc2.1_2.5m_tavg_07.tif&lt;/code&gt;, the average July temperature. Each raster is a 4320 x 8640 grid with global coverage in WGS84 coordinates.&lt;/p&gt;
&lt;p&gt;And use Rasterio to inspect the shape of the raster grid&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;rio&lt;span class="w"&gt; &lt;/span&gt;info&lt;span class="w"&gt; &lt;/span&gt;wc2.1_2.5m_tavg_07.tif&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;jq&lt;span class="w"&gt; &lt;/span&gt;-c&lt;span class="w"&gt; &lt;/span&gt;.shape
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;which prints to stdout, confirming the raster grid shape:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;[4320,8640]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Vector data&lt;/h2&gt;
&lt;p&gt;For our vector dataset, we're using the &lt;a href="https://www.naturalearthdata.com/downloads/50m-cultural-vectors/50m-admin-0-countries-2/"&gt;Natural Earth Admin&lt;/a&gt;
dataset with 241 multipolygons, one for each nation.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;wget&lt;span class="w"&gt; &lt;/span&gt;https://www.naturalearthdata.com/http//www.naturalearthdata.com/download/50m/cultural/ne_50m_admin_0_countries.zip
unzip&lt;span class="w"&gt; &lt;/span&gt;ne_50m_admin_0_countries.zip
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Check the number of features using Fiona&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;fio&lt;span class="w"&gt; &lt;/span&gt;info&lt;span class="w"&gt;  &lt;/span&gt;ne_50m_admin_0_countries.shp&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;jq&lt;span class="w"&gt; &lt;/span&gt;.count
&lt;span class="m"&gt;241&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Overlaying the admin polygons on top of the stylized temperature raster and we get a good picture of the question we're trying to answer:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;What is the historical average temperature of each country in the month of July?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src="assets/img/worldclim-avg-temp-ne-admin.png" width="800px"&gt;&lt;/p&gt;
&lt;h2&gt;Zonal Stats using &lt;code&gt;python-rasterstats&lt;/code&gt;&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;rasterstats&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;zonal_stats&lt;/span&gt;

&lt;span class="n"&gt;stats&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;zonal_stats&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;vector&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;ne_50m_admin_0_countries.shp&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;raster&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;wc2.1_2.5m_tavg_07.tif&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;stats&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;sum&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;mean&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;count&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;std&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;min&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;max&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The time to complete this script was &lt;strong&gt;6.67 seconds&lt;/strong&gt; (fastest of 3 runs).&lt;/p&gt;
&lt;h2&gt;Zonal Stats using &lt;code&gt;postgis_raster&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;To test the performance of the database, we need to get the data in:&lt;/p&gt;
&lt;h3&gt;Load the raster data&lt;/h3&gt;
&lt;p&gt;In part 1, I imported my raster data using a rather naive &lt;code&gt;raster2pgsql&lt;/code&gt; command. This
time, we add a few more options to tune performance.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;raster2pgsql&lt;span class="w"&gt; &lt;/span&gt;-Y&lt;span class="w"&gt; &lt;/span&gt;-d&lt;span class="w"&gt; &lt;/span&gt;-t&lt;span class="w"&gt; &lt;/span&gt;256x256&lt;span class="w"&gt; &lt;/span&gt;-N&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;-3.4e+38&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-I&lt;span class="w"&gt; &lt;/span&gt;-C&lt;span class="w"&gt; &lt;/span&gt;-M&lt;span class="w"&gt; &lt;/span&gt;-n&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;path&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;wc2.1_2.5m_tavg_07.tif&lt;span class="w"&gt; &lt;/span&gt;tavg_07&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;psql
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;-t 256x256&lt;/code&gt; is a key parameter. By cutting the raster into 256-pixel square tiles,
the resulting raster table contains multiple rows, one per tile. A spatial index on the tiles, combined with rewriting the SQL to take advantage of the index and to aggregate across tiles, zonal stats can be made much more efficient inside PostgreSQL.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;-I&lt;/code&gt; indicates that a spatial index of the raster tiles should be built after import. The spatial index, along with a spatial query that can take advantage of it, can quickly select the subset of tiles that overlap your features of interest. &lt;/p&gt;
&lt;p&gt;The other parameters to note:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;-Y&lt;/code&gt; uses COPY for more efficient transfer.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-d&lt;/code&gt; deletes the table if it already exists (useful for testing but careful in production).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-N&lt;/code&gt; defines a nodata value directly at the CLI.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-n&lt;/code&gt; create a &lt;code&gt;path&lt;/code&gt; column to store the filename.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-C&lt;/code&gt; applies constraints to ensure valid raster alignment, etc.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-M&lt;/code&gt; runs &lt;code&gt;VACUUM ANALYZE&lt;/code&gt; on the table as a final step.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Load the vector data&lt;/h3&gt;
&lt;p&gt;Using a standard &lt;code&gt;shp2pgsql&lt;/code&gt; with a &lt;code&gt;-I&lt;/code&gt; to build and index.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;shp2pgsql&lt;span class="w"&gt; &lt;/span&gt;-g&lt;span class="w"&gt; &lt;/span&gt;geometry&lt;span class="w"&gt; &lt;/span&gt;-I&lt;span class="w"&gt; &lt;/span&gt;-s&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;4326&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;ne_50m_admin_0_countries.shp&lt;span class="w"&gt; &lt;/span&gt;countries&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;psql
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;Run the query&lt;/h3&gt;
&lt;p&gt;Now we have two tables loaded, &lt;code&gt;countries&lt;/code&gt; and &lt;code&gt;tavg_07&lt;/code&gt;, and can ask our question in SQL:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ST_SummaryStatsAgg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ST_Clip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;raster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rast&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;countries&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;geometry&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;countries&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;countries&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;geometry&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;geometry&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;n_tiles&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;tavg_07&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;raster&lt;/span&gt;
&lt;span class="k"&gt;INNER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;join&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;countries&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;on&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;ST_INTERSECTS&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;countries&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;geometry&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;raster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rast&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BY&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;geometry&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;I added the &lt;code&gt;GROUP BY&lt;/code&gt; to aggregate across tiles; otherwise we'd get multiple rows per country. And on the SELECT side, PostGIS provides a &lt;code&gt;ST_SummaryStatsAgg&lt;/code&gt; function (the aggregate variant of the &lt;code&gt;ST_SummaryStats&lt;/code&gt;) to sum across tiles.&lt;/p&gt;
&lt;p&gt;Here's the resulting map data rendered via DBeaver. The &lt;code&gt;count&lt;/code&gt; is the number of raster &lt;em&gt;pixels&lt;/em&gt; intersecting the feature, while the &lt;code&gt;n_tiles&lt;/code&gt; is the number of raster &lt;em&gt;tiles&lt;/em&gt;. The &lt;code&gt;mean&lt;/code&gt; is probably what we're interested in; the avergage temperature.&lt;/p&gt;
&lt;p&gt;&lt;img src="assets/img/dbeaver-zonal-results.png"&gt;&lt;/p&gt;
&lt;p&gt;Here's the bottom line on performance: &lt;strong&gt;PostGIS can perform this query in 6.1s&lt;/strong&gt;. Marginally faster than the Python rasterstats version even. It could be that the latest improvements in the geospatial stack account for some of this effect but tiling clearly matters to performance.&lt;/p&gt;
&lt;h2&gt;Effect of tile size&lt;/h2&gt;
&lt;p&gt;The chosen value of &lt;code&gt;-t&lt;/code&gt; determines how much data fits into each tile. There's an unavoidable inverse relationship between the size of a row and the number of rows/tiles. Not surprisingly we find a tradeoff between those two constraints.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;tilesize&lt;/th&gt;
&lt;th&gt;query (s)&lt;/th&gt;
&lt;th&gt;raster2pgsql &lt;br&gt; import (s)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;64x64&lt;/td&gt;
&lt;td&gt;5.9&lt;/td&gt;
&lt;td&gt;58.7&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;256x256&lt;/td&gt;
&lt;td&gt;6.6&lt;/td&gt;
&lt;td&gt;15.8&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1024x1024&lt;/td&gt;
&lt;td&gt;8.5&lt;/td&gt;
&lt;td&gt;7.3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;untiled&lt;/td&gt;
&lt;td&gt;49.2&lt;/td&gt;
&lt;td&gt;5.2&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Smaller tiles with a spatial index means more efficient queries, at the expernse of pre-chopping the raster into many tiles. Depending on the nature of your analysis, you'll want to adjust accordingly. The optimal tilesize is likely to depend on hardware, the tiling patterns of the orignal data and and the usage patterns you expect.&lt;/p&gt;
&lt;p&gt;For this dataset, somewhere around 256x256 appears to be an optimal size. It would make a good default providing the benefits of tiling without as much import overhead as smaller tiles. &lt;/p&gt;
&lt;p&gt;Surprisingly, the &lt;strong&gt;untiled version&lt;/strong&gt; still performs &lt;em&gt;ok&lt;/em&gt; relative to the python code.  The query on an untiled raster is "only" 7.5x slower than the python code, not as bad as the 80x performance hit I found in part 1. While this factor seems highly dependent on the data at hand, the conclusion doesn't change - tiling maters.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Use &lt;code&gt;raster2pgsql -t 256x256 -I&lt;/code&gt; to tile your PostGIS rasters. Combined with aggregate functions and spatial indexes, you get similar zonal stats query functionality and performance from PostGIS as you would with equivalent single-threaded Python/GDAL approaches.&lt;/p&gt;
&lt;p&gt;There's still much to be explored regarding optimal tiling, parallel aggregates, out-of-band rasters, and the impact of source raster data file layout on performance. More to come in part 3... &lt;/p&gt;</content><category term="articles"></category><category term="GIS"></category><category term="python"></category><category term="zonalstats"></category><category term="postgresql"></category><category term="postgis"></category><category term="SQL"></category></entry><entry><title>Zonal Stats with PostGIS Rasters</title><link href="https://www.perrygeo.com/zonal-stats-with-postgis-rasters.html" rel="alternate"></link><published>2018-12-31T00:00:00-07:00</published><updated>2018-12-31T00:00:00-07:00</updated><author><name>Matthew T. Perry</name></author><id>tag:www.perrygeo.com,2018-12-31:/zonal-stats-with-postgis-rasters.html</id><summary type="html">&lt;p&gt;&lt;strong&gt;Zonal statistics&lt;/strong&gt; is a technique to summarize the values of a raster dataset
overlapped by a set of vector geometries.
The analysis can answer queries such as
"&lt;em&gt;Average elevation of each nation park&lt;/em&gt;" or "&lt;em&gt;Maximum temperature by state&lt;/em&gt;".&lt;/p&gt;
&lt;p&gt;My goal in this article is to demonstrate a PostGIS implementation of …&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;strong&gt;Zonal statistics&lt;/strong&gt; is a technique to summarize the values of a raster dataset
overlapped by a set of vector geometries.
The analysis can answer queries such as
"&lt;em&gt;Average elevation of each nation park&lt;/em&gt;" or "&lt;em&gt;Maximum temperature by state&lt;/em&gt;".&lt;/p&gt;
&lt;p&gt;My goal in this article is to demonstrate a PostGIS implementation of zonal stats and
compare the results and runtime performance to a reference Python implementation.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Python with the &lt;a href="https://github.com/perrygeo/python-rasterstats"&gt;&lt;code&gt;rasterstats&lt;/code&gt; library&lt;/a&gt; using GeoTIFF and GeoJSON files.&lt;/li&gt;
&lt;li&gt;SQL queries using &lt;a href="https://postgis.net/docs/RT_reference.html"&gt;PostGIS raster&lt;/a&gt; and vector tables.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;The Dataset&lt;/h2&gt;
&lt;p&gt;For the raster data, let's use the &lt;a href="https://www.eorc.jaxa.jp/ALOS/en/aw3d30/index.htm"&gt;ALOS Global Digital Surface Model&lt;/a&gt; (from the Japan Aerospace Exploration Agency ©JAXA). I picked a 1°x1° tile with 1 arcsecond resolution (roughly 30 meters) in &lt;code&gt;GeoTIFF&lt;/code&gt; format.&lt;/p&gt;
&lt;p&gt;Next, generate 100 random circular polygon features covering the extent of the raster.
The following Python script shows how to do so with the Rasterio and Shapely libs.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="ch"&gt;#!/usr/bin/env python&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;random&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;sys&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;rasterio&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;shapely.geometry&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Point&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;random_features_for_raster&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;steps&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;rasterio&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;x1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bounds&lt;/span&gt;

    &lt;span class="n"&gt;xs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uniform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="n"&gt;ys&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uniform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;y1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;zip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;xs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ys&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
        &lt;span class="n"&gt;buffdist&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uniform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.002&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.04&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;shape&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Point&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buffdist&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="s2"&gt;&amp;quot;type&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Feature&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s2"&gt;&amp;quot;properties&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;name&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)},&lt;/span&gt;
            &lt;span class="s2"&gt;&amp;quot;geometry&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;shape&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;__geo_interface__&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;__main__&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;feat&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;random_features_for_raster&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]):&lt;/span&gt;
        &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;feat&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Piping the features through &lt;code&gt;fio collect&lt;/code&gt; gives us a valid GeoJSON collection with 100 polygon features.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;python&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;make&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;features&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;N035W106_AVE_DSM&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tif&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fio&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;collect&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;regions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;geojson&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Visualizing the data in QGIS shows what we're working with.
The goal is to find basic summary statistics for elevation in each of the regions:&lt;/p&gt;
&lt;p&gt;&lt;img width=500 src="assets/img/20181231_data.jpg"&gt;&lt;/p&gt;
&lt;h2&gt;Python with &lt;code&gt;rasterstats&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;Using &lt;code&gt;zonal_stats&lt;/code&gt; Python function allows you to express the processing at a high-level.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="ch"&gt;#!/usr/bin/env&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;rasterstats&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;zonal_stats&lt;/span&gt;

&lt;span class="n"&gt;features&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;zonal_stats&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;regions.geojson&amp;quot;&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;N035W106_AVE_DSM.tif&amp;quot;&lt;/span&gt;
    &lt;span class="n"&gt;stats&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;count&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;sum&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;mean&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;std&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;min&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;max&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;prefix&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;dem_&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;geojson_out&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;regions_with_elevation.geojson&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;w&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;dst&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;collection&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;type&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;FeatureCollection&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;features&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;features&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
    &lt;span class="n"&gt;dst&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Running this script takes about &lt;strong&gt;2.4 seconds&lt;/strong&gt; and
creates a new GeoJSON file &lt;code&gt;regions_with_elevation.geojson&lt;/code&gt; with the following attributes, as viewed in QGIS&lt;/p&gt;
&lt;p&gt;&lt;img width=500 src="assets/img/20181231_attrs.jpg"&gt;&lt;/p&gt;
&lt;p&gt;And the resulting features can be mapped, in this case using the &lt;code&gt;dem_mean&lt;/code&gt; field to show the average elevation of each
region:&lt;/p&gt;
&lt;p&gt;&lt;img width=500 src="assets/img/20181231_elev.jpg"&gt;&lt;/p&gt;
&lt;h2&gt;Postgis&lt;/h2&gt;
&lt;p&gt;Instead of working with GeoTiff rasters and GeoJSON files, we can perform the same thing in PostGIS tables using SQL.&lt;/p&gt;
&lt;h3&gt;Loading the data&lt;/h3&gt;
&lt;p&gt;To create a raster table name &lt;code&gt;dem&lt;/code&gt; from the GeoTIFF.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;raster2pgsql N035W106_AVE_DSM.tif dem | psql &amp;lt;connection info&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;For some rasters, it might be necessary to explictly set the nodata value.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;UPDATE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;dem&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;SET&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rast&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ST_SetBandNoDataValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rast&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;32768&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;To create a vector table named &lt;code&gt;regions&lt;/code&gt; from the GeoJSON file. (See &lt;a href="https://www.gdal.org/drv_pg.html"&gt;&lt;code&gt;ogr2ogr&lt;/code&gt; docs&lt;/a&gt; for details on the connection info)&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;ogr2ogr&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;PostgreSQL&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;PG:&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&amp;lt;connection info&amp;gt;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;regions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;geojson&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;Zonal Statistics in SQL&lt;/h3&gt;
&lt;p&gt;Now we can express our zonal stats analysis as a SQL statement.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;-- provides: count | sum | mean | stddev | min | max&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ST_SummaryStats&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ST_Clip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rast&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;regions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;wkb_geometry&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;TRUE&lt;/span&gt;&lt;span class="p"&gt;))).&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;regions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;regions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;wkb_geometry&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;geometry&lt;/span&gt;
&lt;span class="k"&gt;INTO&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;regions_with_elevation&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;dem&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;regions&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Let's break that down a bit&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;FROM dem, regions&lt;/code&gt; does a full product of the 100 regions X 1 raster row.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ST_Clip&lt;/code&gt; function clips each raster to the precise geometry of each feature.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ST_SummaryStats&lt;/code&gt; function summarizes each clipped raster and produces a count, sum, mean, standard deviation, min and max column.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;INTO regions_with_elevation&lt;/code&gt; creates a new table with the results.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Conceptually, this approach is similar to the internal process used by &lt;code&gt;rasterstats&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;database&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;min&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;max&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;regions_with_elevation&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;min&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;max&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="n"&gt;mean&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;
&lt;span class="o"&gt;-----+------+------+------------------+-------&lt;/span&gt;
&lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="mh"&gt;32&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;2104&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;2196&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;2141.13257847212&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="mh"&gt;6977&lt;/span&gt;
&lt;span class="mh"&gt;33&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;2296&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;2667&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;2429.01510429154&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="mh"&gt;4171&lt;/span&gt;
&lt;span class="mh"&gt;34&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;1784&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;1917&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1852.97140948564&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="mh"&gt;7485&lt;/span&gt;
&lt;span class="mh"&gt;35&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;2033&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;2144&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;2083.38765260393&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;51768&lt;/span&gt;
&lt;span class="mh"&gt;36&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;1796&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;1843&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1828.69792802617&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="mh"&gt;917&lt;/span&gt;
&lt;span class="mh"&gt;37&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;2072&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;2206&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="mf"&gt;2122.1204719764&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="mh"&gt;8475&lt;/span&gt;
&lt;span class="mh"&gt;38&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;2117&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;2214&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;2152.05270513076&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="mh"&gt;5009&lt;/span&gt;
&lt;span class="mh"&gt;39&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;1915&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;2071&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;2040.61622890496&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;15762&lt;/span&gt;
&lt;span class="p"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Compared to attribute table screenshot above, the results are identical for all columns.
That isn't too surprising given that both approachs use GDAL's rasterization API under the hood.&lt;/p&gt;
&lt;p&gt;Performance is a different story. The zonal stats query took &lt;strong&gt;81.90 seconds&lt;/strong&gt;, roughly 34x slower than the Python code for
the equivalent result.&lt;/p&gt;
&lt;h2&gt;Thoughts&lt;/h2&gt;
&lt;p&gt;In terms of the expressiveness of the two approaches, I can see the appeal of both Python code and SQL queries.
Of course this will be personal preference depending on your background and familiarity with the environments.
The Python API hides the implementation details and is more flexible, with more statistics options and rasterization strategies.
But the SQL approach covers the common use case in a declarative query; it exposes the implementation
details yet remains very readable.&lt;/p&gt;
&lt;p&gt;The performance impact is significant enough to be a deal breaker for PostGIS.
I haven't delved into the issues too closely;
There might some obvious ways to optimize this query but I haven't found anything as of writing this.
PostGIS experts, please get in touch if you find any speedups that I could consider here!&lt;/p&gt;
&lt;p&gt;Performance combined with the additional overhead of managing postgres instances and data imports
tells me that running zonal stats in postgis will not be a great option unless you're already running PostgreSQL.
If your application is already committed to postgres and you want to integrate zonal stats tightly into
your data management strategy, it could be a viable approach.
For example, you could create a &lt;code&gt;TRIGGER&lt;/code&gt; or an asyncronous worker via &lt;code&gt;LISTEN/NOTIFY&lt;/code&gt; to ensure run zonal statistics is run each time a new feature is inserted into your vector table.&lt;/p&gt;
&lt;p&gt;For most other zonal stats use cases, using &lt;code&gt;rasterstats&lt;/code&gt; against local files or in-memory Python data will be a faster with less data management overhead.&lt;/p&gt;</content><category term="articles"></category><category term="GIS"></category><category term="python"></category><category term="zonalstats"></category><category term="postgres"></category><category term="postgis"></category><category term="SQL"></category></entry><entry><title>Processing vector features in Python</title><link href="https://www.perrygeo.com/processing-vector-features-in-python.html" rel="alternate"></link><published>2016-04-16T00:00:00-06:00</published><updated>2016-04-16T00:00:00-06:00</updated><author><name>Matthew T. Perry</name></author><id>tag:www.perrygeo.com,2016-04-16:/processing-vector-features-in-python.html</id><summary type="html">&lt;p&gt;Working with geospatial vector data typically involves manipulating collections of &lt;strong&gt;features&lt;/strong&gt; - points, lines and polygons with attributes. You might change their geometries, alter their properties or both. This is nothing new. Tools like this have been around since the first days of GIS.
Notice the essential role of many of …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Working with geospatial vector data typically involves manipulating collections of &lt;strong&gt;features&lt;/strong&gt; - points, lines and polygons with attributes. You might change their geometries, alter their properties or both. This is nothing new. Tools like this have been around since the first days of GIS.
Notice the essential role of many of these operations: taking vector data as input, doing some work and producing vector data as output.
While conceptually very simple, this logic often gets siloed, tied too closely to our specific implementions, formats, and systems.&lt;/p&gt;
&lt;p&gt;The following is my take on the best practices for designing and building
your own vector processing modules using modern Python. The goals here are not primarily
performance but &lt;strong&gt;interoperability&lt;/strong&gt; and &lt;strong&gt;composability&lt;/strong&gt;.&lt;/p&gt;
&lt;h2&gt;GeoJSON guides the way&lt;/h2&gt;
&lt;p&gt;Using &lt;strong&gt;GeoJSON-like Feature mappings&lt;/strong&gt; as a representation of simple features buys us a ton of interoperability.
It's not only &lt;em&gt;a&lt;/em&gt; standard but the only one that can be translated to fully represent a feature as a python data structure.
Other standards specify file formats or data structures for geometries only.
Most Python modules that deal with geospatial data can speak GeoJSON-like data.
And if they don't, the data structure is easy to construct manually.
Let's take a look at our humble Feature&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;type&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Feature&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;properties&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;name&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Example&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;geometry&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;type&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Point&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;coordinates&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;120.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;42.0&lt;/span&gt;&lt;span class="p"&gt;]}}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;geometry&lt;/code&gt;, the geographic component, is just iterables of lon, lat locations - you can represent points, lines, polygons or multis. The &lt;code&gt;properties&lt;/code&gt; dictionary holds non-geographic information about the features, analagous to the "attribute table" in many GIS.&lt;/p&gt;
&lt;p&gt;A quick note on the term "GeoJSON-like Feature mapping"... GeoJSON is a text serialization format. When we take GeoJSON and translate it into a python data structure it is no longer GeoJSON but a python dictionary (mapping) which follows the semantics of a GeoJSON Feature. From here on out, I'll just refer to this GeoJSON-like python data structure as a &lt;strong&gt;feature&lt;/strong&gt;. If you're writing functions that work with vector data, they should accept and return features.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;That's the convention&lt;/strong&gt;, the golden rule of writing Python vector processing functions&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Functions should take features as inputs and yield or return features.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;In other words, &lt;strong&gt;features in, features out&lt;/strong&gt;. That's it. It's really that simple, and the simplicity buys you a great deal of potential.&lt;/p&gt;
&lt;h2&gt;The IO Sandwich&lt;/h2&gt;
&lt;p&gt;Functions which fit this convention will not read or write to anything outside of locally-scoped variables.
Does your function need to read from a file or write to the network in addition to processing features?
Why should one function be responsible for doing multiple tasks? We're striving for functions that do &lt;em&gt;one&lt;/em&gt; thing - process vector features.&lt;/p&gt;
&lt;p&gt;All the data your function needs should be passed in as arguments. Note that this is very different than passing in a file &lt;em&gt;path&lt;/em&gt; and doing the reading and writing of data within your function:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# BAD&lt;/span&gt;
&lt;span class="n"&gt;process_features&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/path/to/shapefile.shp&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;out.shp&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# GOOD&lt;/span&gt;
&lt;span class="n"&gt;features&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;read_features&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/path/to/shapefile.shp&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;new_features&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;process_features&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;features&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;write_features&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;new_features&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;out.shp&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;You might be concerned about memory. But don't worry, well-behaved Python libraries can use &lt;a href="https://wiki.python.org/moin/Generators"&gt;generators&lt;/a&gt; to load the data as needed.&lt;/p&gt;
&lt;p&gt;Another way to picture it is that your application should build an &lt;strong&gt;IO sandwich&lt;/strong&gt; with all of the reading and writing happening outside of your processing function.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;Read&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Shapefile&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;into&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Features&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;--&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;process_features&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;--&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Write&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Features&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Shapefile&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;That way anyone can use the same function with different inputs and outputs&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;Read&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Web&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;Service&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;into&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Features&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;--&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;process_features&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;--&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Write&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Features&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;PostGIS&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Processing functions should not care where their input features come from or where the output features are going.
As long as &lt;code&gt;process_features&lt;/code&gt; takes and returns features, any number of combinations are possible.&lt;/p&gt;
&lt;p&gt;This not only decouples IO but allows us to &lt;strong&gt;compose&lt;/strong&gt; processes together&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;Read&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Features&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;--&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;process1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;--&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;process2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;--&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;process3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;--&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Write&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Features&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Other guidelines&lt;/h2&gt;
&lt;p&gt;When possible, you should strive for &lt;a href="https://en.wikipedia.org/wiki/Pure_function"&gt;pure functions&lt;/a&gt;; avoid mutating data and return a clean copy.&lt;/p&gt;
&lt;p&gt;Unless you have specific reason, leave the original feature intact except for the thing your function is expected to manipulate. For instance, if your function just alters the geometry, don't drop or change existing properties.&lt;/p&gt;
&lt;p&gt;There are some cases where it makes sense to collect your features into a collection and return the entire thing at once. This will generally occur if the features are not independent. In many cases though, your features will largely be independent and can be processed one-by-one. For these situations, it makes sense to use a generator (i.e. &lt;code&gt;yield feature&lt;/code&gt; instead of &lt;code&gt;return features&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;Finally, you should aim to make your features &lt;em&gt;serializable&lt;/em&gt;. You should be able to &lt;code&gt;json.dump()&lt;/code&gt; the output features. The &lt;code&gt;properties&lt;/code&gt; member should not contain nested dicts which might confuse some GIS formats which require a flat structure. And if possible, avoid extending the json with extra elements outside of &lt;code&gt;properties&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;An Example&lt;/h2&gt;
&lt;p&gt;In this simple example, we'll write a single vector processing function that buffers a geometry by a specified distance.
Taking an input of points, for example:&lt;/p&gt;
&lt;p&gt;&lt;img src="/assets/img/points.png" &gt;&lt;/p&gt;
&lt;p&gt;and buffering them by 10 units.&lt;/p&gt;
&lt;p&gt;&lt;img src="/assets/img/points_buffered.png"&gt;&lt;/p&gt;
&lt;p&gt;Here is the core processing function which follows the &lt;strong&gt;features in, features out&lt;/strong&gt; convention&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;shapely.geometry&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;shape&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;features&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;buffer&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Buffer a feature by specified units&lt;/span&gt;
&lt;span class="sd"&gt;    &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;feature&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;features&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;geom&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;shape&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;feature&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;geometry&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;   &lt;span class="c1"&gt;# Convert to shapely geometry to operate on it&lt;/span&gt;
        &lt;span class="n"&gt;geom&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;geom&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;          &lt;span class="c1"&gt;# Buffer&lt;/span&gt;
        &lt;span class="n"&gt;new_feature&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;feature&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;new_feature&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;geometry&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;geom&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;__geo_interface__&lt;/span&gt;
        &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="n"&gt;new_feature&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Then we could use it in our IO sandwich by reading features from a shapefile and outputing the Features to GeoJSON on stdout. Here's what our &lt;strong&gt;Python interface&lt;/strong&gt; looks like&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;fiona&lt;/span&gt; &lt;span class="c1"&gt;# for input&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;json&lt;/span&gt;  &lt;span class="c1"&gt;# for output&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;process&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;buffer&lt;/span&gt;

&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;fiona&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;data/points.shp&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;feature&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;10.0&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;feature&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;So the python interface is looking good. What if we wanted to use it in a &lt;strong&gt;command line interface&lt;/strong&gt;? Well luckily click and &lt;a href="https://github.com/mapbox/cligj"&gt;cligj&lt;/a&gt; has got the input covered. The &lt;code&gt;@cligj.features_in_arg&lt;/code&gt; reads in an iterable of features from a file, a FeatureCollection or stream of Features.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;click&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;cligj&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;json&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;process&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;buffer&lt;/span&gt;

&lt;span class="nd"&gt;@click&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nd"&gt;@click&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;distance&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nd"&gt;@cligj&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;features_in_arg&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;buffer_cmd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;features&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;distance&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;feature&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;features&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;distance&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;click&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;echo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;feature&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;__main__&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;buffer_cmd&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Which we can then use between &lt;code&gt;fio cat&lt;/code&gt; and &lt;code&gt;fio collect&lt;/code&gt; to process Features in a memory-efficient stream.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;fio&lt;span class="w"&gt; &lt;/span&gt;cat&lt;span class="w"&gt; &lt;/span&gt;data/points.shp&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;python&lt;span class="w"&gt; &lt;/span&gt;buffer_cmd.py&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;fio&lt;span class="w"&gt; &lt;/span&gt;collect&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;points_buffer.geojson
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;What about an &lt;strong&gt;HTTP interface&lt;/strong&gt;? Flask provides us with a lightweight framework to turn our function into a web service:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;flask&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Flask&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Response&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;process&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;buffer&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Flask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vm"&gt;__name__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nd"&gt;@app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/buffer/&amp;lt;distance&amp;gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;methods&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;POST&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;distance&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;collection&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;force&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;distance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;distance&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;new_features&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;features&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;distance&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="n"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;features&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;new_features&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mimetype&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;application/json&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;__main__&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Which gives us a &lt;code&gt;buffer&lt;/code&gt; web service to which you can post GeoJSON FeatureCollections and get back a buffered collection:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;fio&lt;span class="w"&gt; &lt;/span&gt;dump&lt;span class="w"&gt; &lt;/span&gt;data/points.shp&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;curl&lt;span class="w"&gt; &lt;/span&gt;-X&lt;span class="w"&gt; &lt;/span&gt;POST&lt;span class="w"&gt; &lt;/span&gt;-d&lt;span class="w"&gt; &lt;/span&gt;@-&lt;span class="w"&gt; &lt;/span&gt;http://localhost:5000/buffer/10.0&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;points_buffered.geojson
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Writing your vector processing code to follow these simple conventions
enables great flexibility. You can use your code in a Python application,
a command line interface, an HTTP web service - all based on the same core processing functions.
Assuming you can write some glue code to express input and output as GeoJSON features,
this will work with &lt;em&gt;any&lt;/em&gt; vector data source and is not constrained to a single context.
You can use this with any data, anywhere that supports Python. That's a pretty powerful concept,
all made possible by the simple convention of &lt;strong&gt;features in, features out&lt;/strong&gt;.&lt;/p&gt;</content><category term="articles"></category><category term="GIS"></category><category term="python"></category><category term="GeoJSON"></category><category term="Feature"></category></entry><entry><title>Running Python with compiled code on AWS Lambda</title><link href="https://www.perrygeo.com/running-python-with-compiled-code-on-aws-lambda.html" rel="alternate"></link><published>2015-10-10T00:00:00-06:00</published><updated>2015-10-10T00:00:00-06:00</updated><author><name>Matthew T. Perry</name></author><id>tag:www.perrygeo.com,2015-10-10:/running-python-with-compiled-code-on-aws-lambda.html</id><summary type="html">&lt;p&gt;With the recent announcement that AWS Lambda &lt;a href="https://aws.amazon.com/blogs/aws/aws-lambda-update-python-vpc-increased-function-duration-scheduling-and-more/"&gt;now supports Python&lt;/a&gt;, I decided to take a look at using it for geospatial data processing.&lt;/p&gt;
&lt;p&gt;Previously, I had built &lt;a href="https://github.com/Ecotrust/growth-yield-batch"&gt;queue-based systems with Celery&lt;/a&gt; that allow you to run discrete processing tasks in parallel on AWS infrastructure. Just start up as many workers …&lt;/p&gt;</summary><content type="html">&lt;p&gt;With the recent announcement that AWS Lambda &lt;a href="https://aws.amazon.com/blogs/aws/aws-lambda-update-python-vpc-increased-function-duration-scheduling-and-more/"&gt;now supports Python&lt;/a&gt;, I decided to take a look at using it for geospatial data processing.&lt;/p&gt;
&lt;p&gt;Previously, I had built &lt;a href="https://github.com/Ecotrust/growth-yield-batch"&gt;queue-based systems with Celery&lt;/a&gt; that allow you to run discrete processing tasks in parallel on AWS infrastructure. Just start up as many workers on EC2 instances as you need, set up a broker and a results store, add jobs to the queue and collect the results. The problem with this system is that you have to manage all of the infrastructure and services yourself.&lt;/p&gt;
&lt;p&gt;Ideally you wouldn't need to worry about infrastructure at all. That is the promise of AWS Lambda. Lambda can respond to events, fire up a worker and run the task without you needing to worry about provisioning a server. This is especially nice for sporadic work loads in response to events like user-uploaded data where you need to scale up or down regularly.&lt;/p&gt;
&lt;p&gt;The reality of AWS Lambda is that you &lt;em&gt;do&lt;/em&gt; need to worry about infrastructure in a different way. The constraints of the runtime environment mean that you need to get creative if you're doing anything beyond the basics. &lt;strong&gt;If your task relies on compiled code&lt;/strong&gt;, either Python C extensions or shared libraries, you have to jump through some hoops. And for any geo data processing you are going to use a good amount of compiled code to call into C libs (see numpy, rasterio, GDAL, geopandas, Fiona, and so on)&lt;/p&gt;
&lt;p&gt;This article describes my approach to solving the problem of running Python with calls to native code on AWS Lambda.&lt;/p&gt;
&lt;h2&gt;Outline&lt;/h2&gt;
&lt;p&gt;The short version goes like this:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Start an &lt;strong&gt;EC2 instance&lt;/strong&gt; using the official Amazon Linux AMI (based on Red Hat Enterprise Linux)&lt;/li&gt;
&lt;li&gt;On the EC2 insance, Build any &lt;strong&gt;shared libries&lt;/strong&gt; from source.&lt;/li&gt;
&lt;li&gt;Create a &lt;strong&gt;virtualenv&lt;/strong&gt; with all your python dependecies.&lt;/li&gt;
&lt;li&gt;Write a python &lt;strong&gt;handler&lt;/strong&gt; function to respond to events and interact with other parts of AWS (e.g. fetch data from S3)&lt;/li&gt;
&lt;li&gt;Write a python &lt;strong&gt;worker&lt;/strong&gt;, as a command line interface, to process the data&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Bundle&lt;/strong&gt; the virtualenv, your code and the binary libs into a zip file&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Publish&lt;/strong&gt; the zip file to AWS Lambda&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The deployment process is a bit clunky but the benefit is that, once it works, you don't have any servers to manage! A fair tradeoff IMO.&lt;/p&gt;
&lt;p&gt;The process will take a raster dataset uploaded to the input s3 bucket&lt;/p&gt;
&lt;p&gt;&lt;img alt="dem" src="/assets/img/grenada_srtm_raster.png"&gt;&lt;/p&gt;
&lt;p&gt;and automatically extract the shape of the valid data region, placing the resulting GeoJSON in the output s3 bucket.&lt;/p&gt;
&lt;p&gt;&lt;img alt="shape" src="/assets/img/grenada_srtm_shape.png"&gt;&lt;/p&gt;
&lt;h2&gt;Start EC2&lt;/h2&gt;
&lt;p&gt;Under the hood, your Lambda functions are running on EC2 with Amazon Linux. You don't have to think about that at runtime but, if you're calling native compiled code, it needs to be compiled on a similar OS. Theoretically you could do this with your own version of RHEL or CentOS but to be safe it's easier to use the official Amazon Linux since we know that's the exact environment our code will be run in.&lt;/p&gt;
&lt;p&gt;I'm not going to go over the details of &lt;a href="http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/EC2_GetStarted.html"&gt;setting up EC2&lt;/a&gt; so I'll assume we already have our account set up. The AMI ids are listed &lt;a href="https://aws.amazon.com/amazon-linux-ami/"&gt;here&lt;/a&gt;, pick the appropriate one for your region&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;aws&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ec2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;run&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;instances&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="nx"&gt;image&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ami&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="nx"&gt;ff7e8af&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;\
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="nx"&gt;instance&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="k"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;t2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;micro&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;\
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;your&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="nx"&gt;security&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;groups&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;your&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;sg&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;And ssh in&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;ssh&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;your&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="k"&gt;key&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pem&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ec2&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="k"&gt;user&lt;/span&gt;&lt;span class="nv"&gt;@your&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ip&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Make sure everything's up to date:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;sudo yum -y update
sudo yum -y upgrade
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Build shared libraries from source&lt;/h2&gt;
&lt;p&gt;Because your Lambda function will run in a clean AWS linux environment, you can't assume any system libraries will be there. Compiling from source isn't the only option - you could install binaries from the &lt;a href="http://elgis.argeo.org/"&gt;Enterprise Linux GIS&lt;/a&gt; effort but those tend to be older versions. To get more recent libs, compiling from source is an effective approach.&lt;/p&gt;
&lt;p&gt;First install some compile-time deps&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;sudo yum install python27-devel python27-pip gcc libjpeg-devel zlib-devel gcc-c++
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Then build and install proj4 to a local prefix&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;wget https://github.com/OSGeo/proj.4/archive/4.9.2.tar.gz
tar -zvxf 4.9.2.tar.gz
cd proj.4-4.9.2/
./configure --prefix=/home/ec2-user/lambda/local
make
make install
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;And build GDAL, statically linking proj4&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;wget&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="n"&gt;download&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;osgeo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;org&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;gdal&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;1.11&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;gdal&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;1.11&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mf"&gt;3.&lt;/span&gt;&lt;span class="n"&gt;tar&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;gz&lt;/span&gt;
&lt;span class="n"&gt;tar&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;xzvf&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;gdal&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;1.11&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mf"&gt;3.&lt;/span&gt;&lt;span class="n"&gt;tar&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;gz&lt;/span&gt;
&lt;span class="n"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;gdal&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;1.11&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;
&lt;span class="o"&gt;./&lt;/span&gt;&lt;span class="n"&gt;configure&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;prefix&lt;/span&gt;&lt;span class="o"&gt;=/&lt;/span&gt;&lt;span class="n"&gt;home&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;ec2&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;lambda&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;\
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;with&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;geos&lt;/span&gt;&lt;span class="o"&gt;=/&lt;/span&gt;&lt;span class="n"&gt;home&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;ec2&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;lambda&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;local&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;bin&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;geos&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;\
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;with&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;proj4&lt;/span&gt;&lt;span class="o"&gt;=/&lt;/span&gt;&lt;span class="n"&gt;home&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;ec2&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;lambda&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;local&lt;/span&gt;
&lt;span class="n"&gt;make&lt;/span&gt;
&lt;span class="n"&gt;make&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This should leave us with a nice shared library at &lt;code&gt;/home/ec2_user/lambda/local/lib/libgdal.so.1&lt;/code&gt; that can be safely
moved to another AWS Linux box.&lt;/p&gt;
&lt;h2&gt;Create a virtualenv&lt;/h2&gt;
&lt;p&gt;Pretty straighforward but keep in mind that some of the dependecies here are compiled extensions so these builds are platform-specific - which is why we need to build it on the target Amazon Linux OS.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;virtualenv&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;
&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;bin&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;activate&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;GDAL_CONFIG&lt;/span&gt;&lt;span class="o"&gt;=/&lt;/span&gt;&lt;span class="n"&gt;home&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;ec2&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;lambda&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;local&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;bin&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;gdal&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;
&lt;span class="n"&gt;pip&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;install&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rasterio&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Python handler function&lt;/h2&gt;
&lt;p&gt;The handler's job is to respond to the event (e.g. a new file created in an S3 bucket), perform any amazon-specific tasks (like
fetching data from s3) and invoke the worker. Importantly, in the context of this article, the handler
must set the &lt;code&gt;LD_LIBRARY_PATH&lt;/code&gt; to point to any shared libraries that the worker may need.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;subprocess&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;uuid&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;boto3&lt;/span&gt;

&lt;span class="n"&gt;libdir&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getcwd&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;local&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;lib&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;s3_client&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;boto3&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;s3&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Records&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;# Find input/output buckets and key names&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;bucket&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;s3&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;bucket&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;name&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;output_bucket&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="s2"&gt;.geojson&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;s3&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;object&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;key&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;output_key&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="s2"&gt;.geojson&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;# Download the raster locally&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;download_path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/tmp/&lt;/span&gt;&lt;span class="si"&gt;{}{}&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uuid&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uuid4&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;s3_client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;download_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;download_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;# Call the worker, setting the environment variables&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;LD_LIBRARY_PATH=&lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="s1"&gt; python worker.py &amp;quot;&lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="s1"&gt;&amp;quot;&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;libdir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;download_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;output_path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;check_output&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;shell&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;# Upload the output of the worker to S3&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;s3_client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;upload_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;output_path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;output_bucket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;output_key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;output_path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;results&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;It's important that the handler function does not import any modules which require
dynamic linking. For example, you cannot &lt;code&gt;import rasterio&lt;/code&gt; in the main python
handler since the dynamic linker doesn't yet know where to look for the GDAL shared library.
Your can control the linker paths using the &lt;code&gt;LD_LIBRARY_PATH&lt;/code&gt; environment variable
but only &lt;em&gt;before&lt;/em&gt; the process is started. Lambda doesn't give you any control over the environment variables
of the handler function itself.  I
tried hacks like creating new processes within the handler using &lt;code&gt;os.execv&lt;/code&gt; or &lt;code&gt;multiprocessing&lt;/code&gt; pools but the user running the lambda function
doesn't have the necessary permissions to that (both give you &lt;code&gt;OSErrors&lt;/code&gt; - &lt;code&gt;[Errno 13] Permission Denied&lt;/code&gt; and &lt;code&gt;[Errno 38] Function not implemented&lt;/code&gt; respectively).&lt;/p&gt;
&lt;p&gt;Fortunately, Lambda lets you call out to the shell so we can just do our real work through a worker script exposed as a command line interface (details in the next section). While at first this feels clunky, it has the side benefit of forcing separation of your AWS code from your business logic which can be written and tested separately.&lt;/p&gt;
&lt;h2&gt;Worker&lt;/h2&gt;
&lt;p&gt;The worker script can be written in any language, compiled or interpreted, so long as it follows the basic rules of command line interfaces. We're using Python in the handler to set up the appropriate environment. For this example, the worker will &lt;em&gt;also&lt;/em&gt; be written in Python because of it's awesome support for geospatial data processing. But it could be written in Bash or C or just about anything so long as it's runtime environment can be configured with environment variables and arguments.&lt;/p&gt;
&lt;p&gt;In this case, the handler is calling &lt;code&gt;worker.py&lt;/code&gt; which looks like:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;rasterio&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;tempfile&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;NamedTemporaryFile&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;sys&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;rasterio&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;features&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;raster_shape&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;raster_path&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;with&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rasterio&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;raster_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;# read the first band and create a binary mask&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;arr&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;ndv&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nodata&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;binarray&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;arr&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ndv&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;astype&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;uint8&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;# extract shapes from raster&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;shapes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;features&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shapes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;binarray&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;transform&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;# create geojson feature collection&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;fc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;type&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;FeatureCollection&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;features&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]}&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;geom&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;val&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;shapes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;val&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;# not nodata, i.e. valid data&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="n"&gt;feature&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;type&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Feature&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;properties&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;name&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;raster_path&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;geometry&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;geom&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="n"&gt;fc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;features&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;feature&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;# Write to file&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;with&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;NamedTemporaryFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;suffix&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;.geojson&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;delete&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;temp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;temp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fc&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;temp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="vm"&gt;__name__&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;__main__&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;in_path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;out_path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;raster_shape&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;in_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;out_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Notice how the worker itself has no knowledge of AWS events or S3 - it works entirely on the local filesystem and thus can be used in other contexts and tested much more easily.&lt;/p&gt;
&lt;h2&gt;Bundle&lt;/h2&gt;
&lt;p&gt;In order to deploy to Lambda, you need to package it up in a zip file in a slightly unusual manner. All of your Python packages and your handler script should be at the root while the shared libraries can be put in a directory (&lt;code&gt;local/lib&lt;/code&gt; in this case)&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;~/&lt;/span&gt;&lt;span class="nx"&gt;lambda&lt;/span&gt;

&lt;span class="nx"&gt;zip&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;bundle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;zip&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;py&lt;/span&gt;
&lt;span class="nx"&gt;zip&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;r9&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;bundle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;zip&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;worker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;py&lt;/span&gt;
&lt;span class="nx"&gt;zip&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;r9&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;bundle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;zip&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;libgdal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;so&lt;/span&gt;&lt;span class="m m-Double"&gt;.1&lt;/span&gt;

&lt;span class="nx"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="nx"&gt;VIRTUAL_ENV&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;python2&lt;/span&gt;&lt;span class="m m-Double"&gt;.7&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;site&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;packages&lt;/span&gt;
&lt;span class="nx"&gt;zip&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;r9&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;~/&lt;/span&gt;&lt;span class="nx"&gt;lambda&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;bundle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;zip&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;
&lt;span class="nx"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="nx"&gt;VIRTUAL_ENV&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;lib64&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;python2&lt;/span&gt;&lt;span class="m m-Double"&gt;.7&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;site&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;packages&lt;/span&gt;
&lt;span class="nx"&gt;zip&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;r9&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;~/&lt;/span&gt;&lt;span class="nx"&gt;lambda&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;bundle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;zip&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Publish&lt;/h2&gt;
&lt;p&gt;The details of setting up a Lambda function are far too verbose for this article - I would suggest running through the &lt;a href="http://docs.aws.amazon.com/lambda/latest/dg/python-walkthrough-s3-events-adminuser.html"&gt;AWS S3 walkthrough&lt;/a&gt; to get the basic S3 example working first. Then use the AWS CLI to update your existing Lambda function:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;aws lambda update-function-code \
--function-name testfunc1 \
--zip-file fileb://bundle.zip
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h1&gt;The end result&lt;/h1&gt;
&lt;p&gt;Uploading a raster dataset to your S3 bucket should now trigger the Lambda function which will create a new GeoJSON in the output bucket. All automatically invoked based on the S3 events and completely scalable without having to worry about managing or provisioning servers. Nifty!&lt;/p&gt;
&lt;p&gt;The worker and handler code above are intentionally kept short to be more readable. In real usage they would need significantly more error handling and conditionals to handle edge cases, malformed inputs, etc.&lt;/p&gt;
&lt;p&gt;It occured to me after writing this that there really is nothing Python-specific about this approach - the handler could just as easily have been written in Javascript and the worker in some other language. But this should provide a general approach for incorporating native code of any sort in AWS Lambda.&lt;/p&gt;
&lt;p&gt;It remains to be seen if this approach is faster or cheaper than a queue-based system with autoscaled EC2 instances. If you're doing a constantly-high workload with lots of data, it's probably safe to say that Lambda is not appropriate. If you're doing sporadic workloads with some discrete processing task based on user-uploaded data, Lambda might be the ticket. The primary advantage is not necessarily speed or cost but reduced infrastructure complexity and hands-off autoscaling.&lt;/p&gt;</content><category term="articles"></category><category term="python"></category><category term="devops"></category><category term="gdal"></category></entry><entry><title>Python affine transforms</title><link href="https://www.perrygeo.com/python-affine-transforms.html" rel="alternate"></link><published>2015-09-13T00:00:00-06:00</published><updated>2015-09-13T00:00:00-06:00</updated><author><name>Matthew T. Perry</name></author><id>tag:www.perrygeo.com,2015-09-13:/python-affine-transforms.html</id><summary type="html">&lt;p&gt;&lt;em&gt;Raster data coordinate handling with 6-element geotransforms is a pain. Use the &lt;a href="https://github.com/sgillies/affine"&gt;affine&lt;/a&gt; Python library instead.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The typical geospatial coordinate reference system is defined on a cartesian plane with the 0,0 origin in the bottom left and X and Y increasing as you go up and to the right …&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;em&gt;Raster data coordinate handling with 6-element geotransforms is a pain. Use the &lt;a href="https://github.com/sgillies/affine"&gt;affine&lt;/a&gt; Python library instead.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The typical geospatial coordinate reference system is defined on a cartesian plane with the 0,0 origin in the bottom left and X and Y increasing as you go up and to the right. But raster data, coming from its image processing origins, uses a different referencing system to access pixels. We refer to rows and columns with the 0,0 origin in the upper left and rows increase and you move &lt;em&gt;down&lt;/em&gt; while the columns increase as you go right. Still a cartesian plane but not the same one. 
&lt;img alt="xyrowcol" src="/assets/img/xyrowcol.png"&gt;&lt;/p&gt;
&lt;p&gt;So how do you transform between the two? &lt;a href="https://en.wikipedia.org/wiki/Transformation_matrix#Affine_transformations"&gt;Affine transformations&lt;/a&gt; provide a simple way to do it through the use of matrix algebra. Geospatial software of all varieties use an affine transform (sometimes refered to as "geotransform") to go from raster rows/columns to the x/y of the coordinate reference system. Converting from x/y back to row/col uses the inverse of the affine transform. Of course the software implementations vary widely.&lt;/p&gt;
&lt;p&gt;For the remainder, I'll assume the simple case of a non-rotated "north up" raster as that is by far the most common case. &lt;/p&gt;
&lt;p&gt;If you're coming from the matrix algebra perspective, you can ignore the constants in the affine matrix and refer to the the six paramters as &lt;code&gt;a, b, c, d, e, f&lt;/code&gt;. This is the ordering and notation used by the &lt;a href="https://github.com/sgillies/affine"&gt;affine&lt;/a&gt; Python library.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;a&lt;/strong&gt; = width of a pixel&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;b&lt;/strong&gt; = row rotation (typically zero)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;c&lt;/strong&gt;  = x-coordinate of the upper-left corner of the upper-left pixel&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;d&lt;/strong&gt; = column rotation (typically zero)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;e&lt;/strong&gt; = height of a pixel (typically negative)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;f&lt;/strong&gt;  = y-coordinate of the of the upper-left corner of the upper-left pixel&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Perhaps the most pervasive implementation of affine transform encoding in the GIS world is the &lt;a href="http://webhelp.esri.com/arcims/9.3/General/topics/author_world_files.htm"&gt;ESRI World File&lt;/a&gt;. The world file is a simple text file accompanying any raster image which uses six line-separated values in this order:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;a&lt;/strong&gt;  = width of a pixel&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;d&lt;/strong&gt;  = column rotation (typically zero)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;b&lt;/strong&gt;  = row rotation (typically zero)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;e&lt;/strong&gt;  = height of a pixel (typically negative)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;c&lt;/strong&gt;  = x-coordinate of the &lt;em&gt;center&lt;/em&gt; of the upper-left pixel&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;f&lt;/strong&gt;  = y-coordinate of the &lt;em&gt;center&lt;/em&gt; of the upper-left pixel&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It's important to note that the &lt;strong&gt;c&lt;/strong&gt; and &lt;strong&gt;f&lt;/strong&gt; parameters refer to the center of the cell, not the origin!&lt;/p&gt;
&lt;p&gt;GDAL also uses the 6 parameter transform in yet a different order with the "Geotransform" array&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;c&lt;/strong&gt;  = x-coordinate of the upper-left corner of the upper-left pixel&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;a&lt;/strong&gt;  = width of a pixel&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;b&lt;/strong&gt;  = row rotation (typically zero)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;f&lt;/strong&gt;  = y-coordinate of the of the upper-left corner of the upper-left pixel&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;d&lt;/strong&gt;  = column rotation (typically zero)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;e&lt;/strong&gt;  = height of a pixel (typically negative)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;None of those orderings are particularly intutive but at least the first, as implemented by &lt;code&gt;affine&lt;/code&gt;, is "correct" from the matrix algebra perspective. &lt;/p&gt;
&lt;p&gt;For python programmers looking to work with raster data, the &lt;code&gt;osgeo.gdal&lt;/code&gt; library has existed for quite a while. With it the notion of a 6-tuple geotransform in GDAL ordering has become pervasive. And if ordering were the only issue, it wouldn't necessarily be worth switching to the use of the &lt;code&gt;affine&lt;/code&gt; library. The more convincing argument for the use of &lt;code&gt;affine&lt;/code&gt; is the ease with which you can transform coordinates. In other words, why should you have to worry about ordering of parameters at all?&lt;/p&gt;
&lt;p&gt;When dealing with the geotransform as a simple 6-element tuple, you'll probably end up writing code like this to do the actual conversion: &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="gh"&gt;#&lt;/span&gt; Using osgeo.gdal and GDAL geotransform 6-tuples
gt = ds.GetGeoTransform()

&lt;span class="gh"&gt;#&lt;/span&gt; col, row to x, y
x = (col &lt;span class="gs"&gt;* gt[1]) + gt[0]&lt;/span&gt;
&lt;span class="gs"&gt;y = (row *&lt;/span&gt; gt[5]) + gt[3]

&lt;span class="gh"&gt;#&lt;/span&gt; x,y to col,row
col = int((x - gt[0]) / gt[1]) 
row = int((y - gt[3]) / gt[5])
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;I'd be willing to guess that variations of that formula exist in hundreds of python codebases. Not very complicated math but opaque enough not to commit to memory. It's also very easy to slip up ("Is the y origin element 4 or 5?") and introduce non-obvious bugs. Why should such a basic formulation be reimplemented by every programmer? Again, why rely on element ordering at all? &lt;code&gt;affine&lt;/code&gt;, through the use of clever operation overloading, gives you a much simpler interface:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="gh"&gt;#&lt;/span&gt; Using rasterio and affine
a = ds.affine

&lt;span class="gh"&gt;#&lt;/span&gt; col, row to x, y
x, y = a &lt;span class="gs"&gt;* (col, row)&lt;/span&gt;

&lt;span class="gs"&gt;# x, y to col, row&lt;/span&gt;
&lt;span class="gs"&gt;col, row = ~a *&lt;/span&gt; (x, y)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Clean, nice looking code that's harder to get wrong, wouldn't you agree? And as @Asgerpetersen &lt;a href="https://twitter.com/perrygeo/status/643156086229331968"&gt;pointed out&lt;/a&gt;, if there were a non-zero rotation parameter, the affine example would handle it seamlessly while the geotransform formula would fail. &lt;/p&gt;
&lt;p&gt;Also, interoperability with GDAL-style geotransforms is painless&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# construct from our GDAL geotransform&lt;/span&gt;
&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Affine&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;from_gdal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;gt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;gt&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to_gdal&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;As is the ability to read/write from World Files&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;affine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;loadsw&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;dumpsw&lt;/span&gt;

&lt;span class="c1"&gt;# Read from World File&lt;/span&gt;
&lt;span class="k"&gt;with&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;raster.tfw&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;tfw&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;loadsw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tfw&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

&lt;span class="c1"&gt;# Write to World File&lt;/span&gt;
&lt;span class="k"&gt;with&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;other.wld&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;w&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;dest&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;dest&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dumpsw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;With &lt;a href="https://github.com/mapbox/rasterio"&gt;rasterio&lt;/a&gt; planning to deprecate the use of GDAL-style geotransforms in the 1.0 release, it's never too early to start making the switch. Your cleaner raster coordinate code will be well worth the effort. &lt;/p&gt;</content><category term="articles"></category><category term="GIS"></category><category term="python"></category><category term="raster"></category></entry><entry><title>Raspberry Pi: real-time sensor plots with websocketd</title><link href="https://www.perrygeo.com/raspberry-pi-real-time-sensor-plots-with-websocketd.html" rel="alternate"></link><published>2015-03-02T00:00:00-07:00</published><updated>2015-03-02T00:00:00-07:00</updated><author><name>Matthew T. Perry</name></author><id>tag:www.perrygeo.com,2015-03-02:/raspberry-pi-real-time-sensor-plots-with-websocketd.html</id><summary type="html">&lt;p&gt;This year I'm starting to delve into some electronics projects and hardware hacking.
What follows is an account of my first end-to-end Raspberry Pi project. 
In terms of functionality, it doesn't do much at the moment - just reads from 
a photoresistor sensor and plots the light levels in the corner …&lt;/p&gt;</summary><content type="html">&lt;p&gt;This year I'm starting to delve into some electronics projects and hardware hacking.
What follows is an account of my first end-to-end Raspberry Pi project. 
In terms of functionality, it doesn't do much at the moment - just reads from 
a photoresistor sensor and plots the light levels in the corner of my office. Eventually,
I want to hook up a couple of light, moisture and temperature sensors throughout 
my garden to do some science experiments and/or remind myself to water 
the tomatoes. This is but the first step
in that larger project...&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;The Pi is wired up to a 3.3v circuit with a photoresistor.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The state of the digital input pins are read by a python program.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The readings are streamed to a websocket via log file.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The HTML/Javascript interface connects with the websocket and plots the values in real time. &lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="assets/img/rpi_websockets.png"&gt;&lt;/p&gt;
&lt;p&gt;Although it's all just for fun at this point, I've discovered a lot of great unix networking 
tools and javascript libraries
that will come in handy in my day job as well. Here's the details on how it all came together...&lt;/p&gt;
&lt;h2&gt;The circuit&lt;/h2&gt;
&lt;p&gt;I implemented the design 
from &lt;a href="https://learn.adafruit.com/basic-resistor-sensor-reading-on-raspberry-pi/basic-photocell-reading"&gt;the adafruit tutorial&lt;/a&gt; on the subject. The adafruit
image shows the basic idea:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://learn.adafruit.com/system/assets/assets/000/001/321/medium800/raspberry_pi_photocell.jpg?1396770994"&gt;&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;photoresistor&lt;/strong&gt; provides increased resistance to electric current as the visible light becomes dimmer. Conversely, resistance decreases as light becomes brighter. It is an analog sensor but 
the Raspberry Pi only has digital inputs (the general purpose input output or GPIO pins). 
To solve that, we can employ a capacitor using "RC timing". &lt;/p&gt;
&lt;p&gt;A &lt;strong&gt;capacitor&lt;/strong&gt; builds up voltage 
over time and, when this voltage hits ~1.4V, the digital input pin reads "high". So 
instead of taking a direct analog reading, we set a loop and time how long it 
takes for the capacitor to "fill up". &lt;/p&gt;
&lt;p&gt;If the time interval is small (i.e. the capacitor is charging rapidly), there 
is less resistance from our analog sensor which means more light. If the time 
interval is large (i.e. the capacitor is taking a long time to charge on each cycle),
there is more resistance and less light.&lt;/p&gt;
&lt;p&gt;Wired up to the photoresistor on my 25 year-old Radio Shack Electronics Learning Lab,
it looks a bit clunkier but still does the trick:&lt;/p&gt;
&lt;p&gt;&lt;img src="/assets/img/withpi.png" alt="withpi"&gt;&lt;/p&gt;
&lt;p&gt;Quick side note: The ribbon and connectors between the raspberry pi and the breadboard are called
a &lt;a href="http://www.adafruit.com/product/914"&gt;Pi Cobber&lt;/a&gt;. It makes working with the 
GPIO pins easier but, as you can tell from the photos, the incoming cable obstructs access a bit.
I might take a look at the &lt;a href="https://www.adafruit.com/products/1105"&gt;T-Cobbler&lt;/a&gt;
which promises to clear up some vertical space on the breadboard.&lt;/p&gt;
&lt;h2&gt;Reading digital input pins from an analog sensor&lt;/h2&gt;
&lt;p&gt;In order to read input from our analog pins, we can use the &lt;a href="https://pypi.python.org/pypi/RPi.GPIO"&gt;RPi.GPIO&lt;/a&gt; python library. &lt;/p&gt;
&lt;p&gt;There's not much more that I can add to &lt;a href="https://learn.adafruit.com/basic-resistor-sensor-reading-on-raspberry-pi/basic-photocell-reading"&gt;the adafruit tutorial&lt;/a&gt; which covers the topic well.  I made a few modifications:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;output a unix timestamp along with the reading&lt;/li&gt;
&lt;li&gt;flush the output to &lt;code&gt;stdout&lt;/code&gt; after every reading to make sure the output isn't buffered.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;__main__&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;

    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
         &lt;span class="c1"&gt;# Get sensor timing and unix timestamp&lt;/span&gt;
         &lt;span class="n"&gt;reading&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;RCtime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;18&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
         &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
         &lt;span class="n"&gt;timestamp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;to_unix_timestamp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

         &lt;span class="nb"&gt;print&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;reading&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
         &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stdout&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;flush&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;You can read the complete &lt;a href="https://github.com/perrygeo/pi_sensor_realtime/blob/master/read_sensor.py"&gt;read_sensor.py script&lt;/a&gt; on github. &lt;/p&gt;
&lt;p&gt;With the script in place and the circuit wired up, I can fire up the script &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;sudo python read_sensor.py
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;and see the timestamp and sensor reading written to the console as comma-separated values:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="mf"&gt;1425505117.05&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;793&lt;/span&gt;
&lt;span class="mf"&gt;1425505117.16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;802&lt;/span&gt;
&lt;span class="mf"&gt;1425505117.38&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;768&lt;/span&gt;
&lt;span class="mf"&gt;1425505117.82&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;709&lt;/span&gt;
&lt;span class="mf"&gt;1425505117.93&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;801&lt;/span&gt;
&lt;span class="mf"&gt;1425505118.05&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;798&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;So what do those values mean? They represent a count of the number of cycles it took to 
charge the capacitor. Not a meaningful number by itself but it could be calibrated to 
use standard units or simply used as relative values (lower value == brighter light)&lt;/p&gt;
&lt;p&gt;It's important to note that, on a Linux machine, you can't be guaranteed that your
event loop won't get interrupted by other processes. So you probably shouldn't use Linux
as a real-time sensor platform directly. However, it works well enough for demonstration
provided your Raspberry Pi isn't bogged down by other CPU-intensive processes.&lt;/p&gt;
&lt;p&gt;Another caveat with this approach - we can only use a &lt;em&gt;single process&lt;/em&gt; to access the GPIO
pins in this manner. Having multiple processes or threads setting/reading GPIO pin states 
would cause inaccuracies as each process could reset the pins mid-cycle and interrupt 
the timing of other processes. &lt;/p&gt;
&lt;h2&gt;Streaming websockets&lt;/h2&gt;
&lt;!-- &lt;iframe border=0 frameborder=0 height=410 width=550 src="http://twitframe.com/show?url=https%3A%2F%2Ftwitter.com%2Fperrygeo%2Fstatus%2F570721261715742720"&gt;
 &lt;/iframe&gt; --&gt;

&lt;p&gt;Websockets are an extension to HTTP that allow data to be sent &lt;em&gt;from&lt;/em&gt; a server &lt;em&gt;to&lt;/em&gt; a client 
using a persistent connection. Think pushing notification messages. &lt;/p&gt;
&lt;p&gt;&lt;a href="http://websocketd.com"&gt;websocketd&lt;/a&gt; allows you to 
take the standard output from any unix program and publish it on a 
websocket. It can also work with standard input, opening up the doors for some
amazing software workflows: imagine taking any well behaved Unix command and immediately
wrapping it's functionality in a web protocol! &lt;/p&gt;
&lt;p&gt;To output the sensor readings using a websocket, I'll first run the &lt;code&gt;read_sensor.py&lt;/code&gt; script in the background with high priority (&lt;code&gt;nice -20&lt;/code&gt;) and redirect the output to a logfile:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;nice&lt;span class="w"&gt; &lt;/span&gt;-20&lt;span class="w"&gt; &lt;/span&gt;python&lt;span class="w"&gt; &lt;/span&gt;read_sensor.py&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;log.txt&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;amp;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Then I will run &lt;code&gt;websocketd&lt;/code&gt; on port 8080, serve a few static 
files and provide a command to run.
In this case, the command is the basic unix &lt;code&gt;tail -f&lt;/code&gt; which streams the contents of the log file.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;websocketd&lt;span class="w"&gt; &lt;/span&gt;--port&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;8080&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;--staticdir&lt;span class="o"&gt;=&lt;/span&gt;./static&lt;span class="w"&gt; &lt;/span&gt;tail&lt;span class="w"&gt; &lt;/span&gt;-f&lt;span class="w"&gt; &lt;/span&gt;log.txt&lt;span class="w"&gt; &lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Now the sensor readings are being logged and a websocket server is running. 
For each client that connects to the websocket, a new process (&lt;code&gt;tail -f log.txt&lt;/code&gt;) will be started
and &lt;code&gt;stdout&lt;/code&gt; will be streamed to that client via websocket messages.&lt;/p&gt;
&lt;p&gt;Note that the &lt;code&gt;tail -f&lt;/code&gt; command is &lt;em&gt;not&lt;/em&gt; yet running until a websocket client makes a 
connection. Because it runs in its own process and simply reads the sensor log file, 
we can start as many of them as our hardware can handle.&lt;/p&gt;
&lt;p&gt;In summary, the pattern is: run a single process that reads from the GPIO pins and writes to a sensor log, then fire off multiple processes that read the log and stream the output over websockets.&lt;/p&gt;
&lt;p&gt;Now we're ready to test it. &lt;/p&gt;
&lt;h2&gt;HTML/Javascript interface&lt;/h2&gt;
&lt;p&gt;Working with websockets in Javascript is fairly straightforward. First, create a connection&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ws&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;WebSocket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;ws://example.org:8080/&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;then set some callbacks to handle incoming messages from the server.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onmessage&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;We got something:&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Websockets are built into almost every modern browser so this functionality 
works out of the box. But if the connection is lost for any reason, the native Websocket
implementations do not automatically reconnect. To solve that problem, 
there is &lt;a href="https://github.com/joewalnes/reconnecting-websocket"&gt;ReconnectingWebSocket&lt;/a&gt; which does exactly what it sounds like; attempts to 
reconnect automatically when needed.&lt;/p&gt;
&lt;p&gt;Then to create an animated real time plot of the streaming data, you'll need a javascript library like &lt;a href="http://smoothiecharts.org/"&gt;Smoothie Charts&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I should also note that the server (&lt;a href="http://websocketd.com"&gt;websocketd&lt;/a&gt;), the javascript plotting library (&lt;a href="http://smoothiecharts.org/"&gt;Smoothie Charts&lt;/a&gt;), and the javascript networking library (&lt;a href="https://github.com/joewalnes/reconnecting-websocket"&gt;ReconnectingWebSocket&lt;/a&gt;) were all written by &lt;a href="https://github.com/joewalnes/"&gt;joewalnes&lt;/a&gt; - this guy is responsible for making the three biggest pieces of this system and deserves mad props! &lt;/p&gt;
&lt;p&gt;All of the HTML and js can be found here: &lt;a href="https://github.com/perrygeo/pi_sensor_realtime/blob/master/static/index.html"&gt;index.html&lt;/a&gt;. &lt;/p&gt;
&lt;p&gt;Finally, here is the result. A streaming, real time plot of sensor readings. This clip was recorded as I came into my office, opened a few 
windows and turned on a light. As the room gets brighter, you can see the sensor readings drop, and then rise again as I pass my hand over sensor a few times to block the light.&lt;/p&gt;
&lt;!-- &lt;img src="assets/img/rpi_plot.png"&gt; --&gt;

&lt;iframe id="player" type="text/html" width="640" height="390"
  src="https://www.youtube.com/embed/CfwRj3HP3j0?enablejsapi=1&amp;origin=http://example.com"
  frameborder="0"&gt;&lt;/iframe&gt;

&lt;p&gt;Maybe not incredibly useful in it's current state but it provided an excellent learning experience to work on the entire stack, integrating electronics and hardware with web software. It opens the doors for all sorts of new projects. All of the code is available on my &lt;a href="https://github.com/perrygeo/pi_sensor_realtime"&gt;github repo&lt;/a&gt;. Any questions? Shoot me an email or message on twitter. I'm a beginner when it comes to electrical theory so somebody please correct me if I'm way off the mark on something. &lt;/p&gt;</content><category term="articles"></category></entry><entry><title>Zonal statistics: histograms as user-defined aggregate functions</title><link href="https://www.perrygeo.com/zonal-statistics-histograms-as-user-defined-aggregate-functions.html" rel="alternate"></link><published>2015-02-23T00:00:00-07:00</published><updated>2015-02-23T00:00:00-07:00</updated><author><name>Matthew T. Perry</name></author><id>tag:www.perrygeo.com,2015-02-23:/zonal-statistics-histograms-as-user-defined-aggregate-functions.html</id><summary type="html">&lt;p&gt;sthoijslkfjaslfjlasglaskglsdfjgdjsflkgjdsfl  rglkje&lt;/p&gt;</summary><content type="html">&lt;h2&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Zonal statistics allow you to summarize raster datasets based on vector geometries 
by aggregating all pixels associated with each vector feature, typically to a single scalar value. For example, you might want the &lt;em&gt;mean&lt;/em&gt; elevation of each country against an SRTM Digital Elevation
Model (DEM). This is easily accomplished in python using &lt;a href="https://github.com/perrygeo/python-raster-stats"&gt;&lt;code&gt;rasterstats&lt;/code&gt;&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;rasterstats&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;zonal_stats&lt;/span&gt;
&lt;span class="n"&gt;stats&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;zonal_stats&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;countries.shp&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;elevation.tif&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;stats&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;mean&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;copy_properties&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;stats&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;name&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;mean&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Which would give us output similar to below, with the mean elevation (meters) for each country:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;Afghanistan 1826.38
Netherlands 8.78
Nepal 2142.28
Zimbabwe 980.85
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Zonal Histograms&lt;/h2&gt;
&lt;p&gt;Using the built-in aggregate functions in &lt;code&gt;rasterstats&lt;/code&gt; can reveal a lot about 
about the underlying raster dataset (see &lt;a href="https://github.com/perrygeo/python-raster-stats#statistics"&gt;statistics&lt;/a&gt; for full list). Most of the time the standard descriptive statistics
like min, max, mean, median, etc. can tell us everything we need to know.&lt;/p&gt;
&lt;p&gt;But what if we want to retain more information about the underlying distribution of
values? Instead of simply stating &lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Afghanistan is, on average, 1826.38 meters above sea level&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;supposed we wanted to see how much of the country is in high vs low elevation areas.
We could bin the elevations into meaningful ranges (say 0-200 meters, 200 to 400 meters, etc) and create a histogram of pixel counts to show the shape of the underlying distribution. In this case, the aggregate function does not return a scalar value but a dictionary with 
each bin as a key.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&amp;gt;&amp;gt; stats[&amp;#39;elevation_histogram&amp;#39;]
{&amp;#39;0 to 500m&amp;#39;: ...,
 &amp;#39;500 to 1000m&amp;#39;: ...,
 &amp;#39;1000 to 3000m&amp;#39;:...,
 &amp;#39;3000 to 5000m&amp;#39;:...,
 &amp;#39;5000m+&amp;#39;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;That's the goal, now how do we accomplish this? &lt;/p&gt;
&lt;h2&gt;User-defined aggregate functions&lt;/h2&gt;
&lt;p&gt;Because a histogram might need to &lt;a href="http://docs.scipy.org/doc/numpy/reference/generated/numpy.histogram.html"&gt;specify a number of arguments&lt;/a&gt; to customize the results, it's not
feasible for &lt;code&gt;rasterstats&lt;/code&gt; to define a generic histogram function. However, as of &lt;a href="https://pypi.python.org/pypi/rasterstats/0.6.1"&gt;version 0.6&lt;/a&gt;, we
have the ability to create custom, user-defined aggregate functions such as the zonal histogram 
idea described above.&lt;/p&gt;
&lt;p&gt;First, we have to write our function. The first and only argument is a masked numpy array 
and will typically be handled by &lt;code&gt;numpy&lt;/code&gt; functions. The function's return value will be added to the stats output for each feature. The returned
value does &lt;em&gt;not&lt;/em&gt; need to be a scalar, it can be any valid python value (though it's probably
best to stick with dicts, lists and other simple data structures that are easily 
serializable).&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;numpy&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;np&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;itertools&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;elevation_histogram&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;bin_edges&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10000&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;hist&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;histogram&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;bins&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;bin_edges&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;upper&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;itertools&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;izip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bin_edges&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;bin_edges&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;hist&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="s2"&gt; to &lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="s2"&gt;m&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;upper&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;And then add our custom &lt;code&gt;elevation_histogram&lt;/code&gt; function to our &lt;code&gt;zonal_stats&lt;/code&gt; call
using the &lt;code&gt;add_stats&lt;/code&gt; keyword argument:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;stats = zonal_stats(&amp;#39;countries.shp&amp;#39;, &amp;#39;elevation.tif&amp;#39;, copy_properties=True,
                    add_stats={&amp;#39;elevation_histogram&amp;#39;: elevation_histogram})
for s in stats:
    print s[&amp;#39;name&amp;#39;], s[&amp;#39;mean&amp;#39;], s[&amp;#39;elevation_histogram&amp;#39;]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;which gives us output similar to the following which gives you raw pixel counts
for each of the elevation bins (formatted for readability)&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;Afghanistan 1826.38 {
    &amp;#39;3000 to 5000m&amp;#39;: 1099730, 
    &amp;#39;0 to 400m&amp;#39;: 1754317,
    &amp;#39;1000 to 3000m&amp;#39;: 2884917, 
    &amp;#39;5000 to 10000m&amp;#39;: 83158, 
    &amp;#39;400 to 1000m&amp;#39;: 1907790}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The only caveat with using this technique is that nested dictionaries and other 
non-scalar values might cause difficulty when trying to serialize this 
data structure to other formats. For example, most GIS formats don't support hierarchical 
properties (nested dictionaries) so you might have to flatten the data before
writing to e.g. PostGIS or an ESRI shapefile. &lt;/p&gt;
&lt;p&gt;With the ability to write user-defined aggregate functions, I can keep the core
of &lt;code&gt;rasterstats&lt;/code&gt; light while allowing for the possibility of complex aggregate analysis
that might be needed in the future. Good stuff.&lt;/p&gt;</content><category term="articles"></category></entry><entry><title>Topological simplification of simple features</title><link href="https://www.perrygeo.com/topological-simplification-of-simple-features.html" rel="alternate"></link><published>2015-01-11T00:00:00-07:00</published><updated>2015-01-11T00:00:00-07:00</updated><author><name>Matthew T. Perry</name></author><id>tag:www.perrygeo.com,2015-01-11:/topological-simplification-of-simple-features.html</id><summary type="html">&lt;h2&gt;The case for topology&lt;/h2&gt;
&lt;p&gt;&lt;a href="http://en.wikipedia.org/wiki/Simple_Features"&gt;Simple feature&lt;/a&gt; representations
of polygon geometries are ubiquitous due to their ease of use.
Thinking of spatial features as having a single, independent geometry is easy and fits most use cases.
But that ease of use disappears when we need to represent the topological
relationship between …&lt;/p&gt;</summary><content type="html">&lt;h2&gt;The case for topology&lt;/h2&gt;
&lt;p&gt;&lt;a href="http://en.wikipedia.org/wiki/Simple_Features"&gt;Simple feature&lt;/a&gt; representations
of polygon geometries are ubiquitous due to their ease of use.
Thinking of spatial features as having a single, independent geometry is easy and fits most use cases.
But that ease of use disappears when we need to represent the topological
relationship between features.&lt;/p&gt;
&lt;p&gt;In this article, I'll focus on one particular task with simple features data that
would benefit from topology - namely simplifying a polygon dataset by removing vertices.
Here's the original dataset, a 30+MB shapefile with very dense line work.&lt;/p&gt;
&lt;p&gt;&lt;img alt="original" src="/images/topo_simplify/original.png"&gt;&lt;/p&gt;
&lt;p&gt;Geometries &lt;em&gt;can&lt;/em&gt; be simplified under the Simple Features model but, since
each geometry is processed independently, the &lt;strong&gt;topological relationships between
features can be disrupted&lt;/strong&gt;. For instance, using the &lt;code&gt;Simplify Geometries&lt;/code&gt; tool in
QGIS, I can simplify the polygons dramatically but we see gaps between polygons
and other side effects.&lt;/p&gt;
&lt;p&gt;&lt;img alt="no_topology" src="/images/topo_simplify/no_topology.png"&gt;&lt;/p&gt;
&lt;h2&gt;The plan&lt;/h2&gt;
&lt;p&gt;Because we'll need to &lt;em&gt;build&lt;/em&gt; topology before acting on it, the process for simplifying
simple features datasets involves converting the data to topological structure,
simplifying it, then converting it back to a simple features representation.&lt;/p&gt;
&lt;p&gt;Many of the big GIS systems (ESRI's .e00, ArcInfo "coverages", and GRASS vectors)
have their own topological data structures. More recently, we've seen the rise of
Open Street Map (OSM) format and TopoJSON, both of which model topological relationships.&lt;/p&gt;
&lt;p&gt;Of these options, I selected &lt;a href="https://github.com/mbostock/topojson/wiki"&gt;TopoJSON&lt;/a&gt;
because of it's robust &lt;a href="https://github.com/mbostock/topojson/wiki/Command-Line-Reference"&gt;command-line tool&lt;/a&gt;
which handles building topology and simplification in one step. Additionally, it
works with GeoJSON and Shapefile inputs, two of the most common
data formats for simple features.&lt;/p&gt;
&lt;p&gt;The workflow goes something like this:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Convert data into a shapefile with the EPSG:4326 spatial reference (lonlat, wgs84)&lt;/li&gt;
&lt;li&gt;Convert to topojson and simplify&lt;/li&gt;
&lt;li&gt;Convert to geojson&lt;/li&gt;
&lt;li&gt;Optionally, convert geojson to other formats supported by OGR&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;To follow along, you'll need to have the following software installed:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;GDAL command line utilities (we'll use &lt;code&gt;ogr2ogr&lt;/code&gt; at the command line)&lt;ul&gt;
&lt;li&gt;&lt;code&gt;apt-get install gdal-bin&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;topojson&lt;/code&gt; command line utility&lt;ul&gt;
&lt;li&gt;&lt;code&gt;npm install -g topojson&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Python with the &lt;code&gt;shapely&lt;/code&gt; package installed.&lt;ul&gt;
&lt;li&gt;&lt;code&gt;pip install shapely&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Step 1: Convert to WGS84 shapefile&lt;/h2&gt;
&lt;p&gt;If you're already working with an ESRI Shapefile or GeoJSON format and your data
is already in unprojected WGS84 coordinates (i.e. EPSG:4326), you can skip to step 2.&lt;/p&gt;
&lt;p&gt;Otherwise, &lt;code&gt;ogr2ogr&lt;/code&gt; makes that conversion simple:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;ogr2ogr&lt;span class="w"&gt; &lt;/span&gt;-t_srs&lt;span class="w"&gt; &lt;/span&gt;epsg:4326&lt;span class="w"&gt; &lt;/span&gt;-f&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;ESRI Shapefile&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;ecoregions_original.shp&lt;span class="w"&gt; &lt;/span&gt;EcoregionSummaries3.gdb.zip&lt;span class="w"&gt; &lt;/span&gt;EcoRegions
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Step 2: Convert to TopoJSON and simplify&lt;/h2&gt;
&lt;p&gt;The simplification, quantization (more on that later) and the conversion to
a topological data model are handled by &lt;code&gt;topojson&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;You have two options for specifying how aggressively you want to simplify your data.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Use a tolerance, specified in &lt;a href="http://en.wikipedia.org/wiki/Steradian#SI_multiples"&gt;steridians&lt;/a&gt; with the &lt;code&gt;-s&lt;/code&gt; flag&lt;/li&gt;
&lt;li&gt;Use a proportion of points, 0 to 1, to retain with the &lt;code&gt;--simplify-proportion&lt;/code&gt; flag&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;One quirk of the topojson implementation is that it uses a relatively low quantization factor by default.
Effectively, this snaps coordinates to a grid in order to save space and simplify geometries even further.
This yields nice small coordinates but can result in a "stair step" effect at higher
zoom levels. The default is &lt;code&gt;-q 1E4&lt;/code&gt; but I've found good results with &lt;code&gt;-q 1E6&lt;/code&gt; as
recommended in the topojson docs.&lt;/p&gt;
&lt;p&gt;As an example, let's take our &lt;code&gt;ecoregions_original.shp&lt;/code&gt; and convert it to topojson
with a tolerance of &lt;code&gt;1E-8&lt;/code&gt; steridians. We want to make sure we explicitly mention
that the data is in spherical (unprojected) coordinates and to retain the properties
of the original attribute table:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;topojson&lt;span class="w"&gt; &lt;/span&gt;--spherical&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;--properties&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;-s&lt;span class="w"&gt; &lt;/span&gt;1E-8&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;-q&lt;span class="w"&gt; &lt;/span&gt;1E6&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;-o&lt;span class="w"&gt; &lt;/span&gt;temp.topojson&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;ecoregions_original.shp
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Step 3: Convert to GeoJSON&lt;/h2&gt;
&lt;p&gt;This part was a bit trickier than I anticipated. Luckily Sean Gillies has written
some preliminary &lt;a href="http://sgillies.net/blog/1159/topojson-with-python"&gt;python functions&lt;/a&gt;
for converting topojson geometries to standard GeoJSON-like python dictionaries.&lt;/p&gt;
&lt;p&gt;In order to make a higher-level conversion utility, I started working on &lt;a href="https://gist.github.com/perrygeo/1e767e42e8bc54ad7262#file-topo2geojson-py"&gt;topo2geojson.py&lt;/a&gt; which provides a command line
interface to perform TopoJSON to GeoJSON conversions.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;python&lt;span class="w"&gt; &lt;/span&gt;topo2geojson.py&lt;span class="w"&gt; &lt;/span&gt;temp.topojson&lt;span class="w"&gt; &lt;/span&gt;ecoregions_simple.geojson
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;There is some additional logic to ensure validity of polygons though it is very
basic and I'm sure there are ways to make the geometry conversions more robust.
Please note that I've only tested this script on this one dataset and it likely needs
additional work to be considered a full-fledged conversion tool; consider it more of a
starting point than an out-of-the box solution.&lt;/p&gt;
&lt;h2&gt;Optional Step 4: Convert to any OGR format&lt;/h2&gt;
&lt;p&gt;Once data is in GeoJSON format, we're free to do what we want with it, including
converting it back to a shapefile or any other OGR supported data format.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;ogr2ogr&lt;span class="w"&gt; &lt;/span&gt;-f&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;ESRI Shapefile&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;ecoregions_simple.shp&lt;span class="w"&gt; &lt;/span&gt;ecoregions_simple.geojson&lt;span class="w"&gt; &lt;/span&gt;OGRGeoJson
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h1&gt;Case study: evaluating simplification tolerances&lt;/h1&gt;
&lt;p&gt;In the remainder of this article, I'll walk through a demonstration of these steps
in order to find an optimal simplification tolerance for my test data. The optimal
tolerance depends on your needs, what scales you will be using your data and how
aggressively you need to reduce file size. Ultimately, it's a ** tradeoff between
low geometry size and accurate line work**.&lt;/p&gt;
&lt;p&gt;We can easily script this solution in order to test multiple simplification tolerances.
As a bonus, we can fire off multiple iterations at once to leverage multiple cores.
Since I've got 4 cores on my laptop, I can run 4 processes in nearly the same time
it takes to run 1 using some simple shell tricks (Linux/OSX only; sorry Windows users but I don't know .bat files well enough to demonstrate)&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;tolerance&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;1E-7&lt;span class="w"&gt; &lt;/span&gt;1E-8&lt;span class="w"&gt; &lt;/span&gt;1E-9&lt;span class="w"&gt; &lt;/span&gt;1E-10
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;topojson&lt;span class="w"&gt; &lt;/span&gt;--spherical&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;--properties&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;-s&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$tolerance&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;-q&lt;span class="w"&gt; &lt;/span&gt;1E6&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;-o&lt;span class="w"&gt; &lt;/span&gt;temp_&lt;span class="nv"&gt;$tolerance&lt;/span&gt;.topojson&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;ecoregions_original.shp&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;# Convert it to GeoJSON&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;python&lt;span class="w"&gt; &lt;/span&gt;topo2geojson.py&lt;span class="w"&gt; &lt;/span&gt;temp_&lt;span class="nv"&gt;$tolerance&lt;/span&gt;.topojson&lt;span class="w"&gt; &lt;/span&gt;temp_&lt;span class="nv"&gt;$tolerance&lt;/span&gt;.geojson&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;# Optionally, convert GeoJSON to any OGR data source&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;ogr2ogr&lt;span class="w"&gt; &lt;/span&gt;-f&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;ESRI Shapefile&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;ecoregions_&lt;span class="nv"&gt;$tolerance&lt;/span&gt;.shp&lt;span class="w"&gt; &lt;/span&gt;temp_&lt;span class="nv"&gt;$tolerance&lt;/span&gt;.geojson&lt;span class="w"&gt; &lt;/span&gt;OGRGeoJson&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;amp;&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;done&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;wait&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Then we can take a look at the resulting .topojson file sizes&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;ls&lt;span class="w"&gt; &lt;/span&gt;-lh&lt;span class="w"&gt; &lt;/span&gt;*.topojson
-rw-rw-r--&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;mperry&lt;span class="w"&gt; &lt;/span&gt;mperry&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;.5M&lt;span class="w"&gt; &lt;/span&gt;Jan&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;11&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;12&lt;/span&gt;:25&lt;span class="w"&gt; &lt;/span&gt;temp_1E-10.topojson
-rw-rw-r--&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;mperry&lt;span class="w"&gt; &lt;/span&gt;mperry&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;.1M&lt;span class="w"&gt; &lt;/span&gt;Jan&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;11&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;12&lt;/span&gt;:25&lt;span class="w"&gt; &lt;/span&gt;temp_1E-9.topojson
-rw-rw-r--&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;mperry&lt;span class="w"&gt; &lt;/span&gt;mperry&lt;span class="w"&gt; &lt;/span&gt;869K&lt;span class="w"&gt; &lt;/span&gt;Jan&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;11&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;12&lt;/span&gt;:25&lt;span class="w"&gt; &lt;/span&gt;temp_1E-8.topojson
-rw-rw-r--&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;mperry&lt;span class="w"&gt; &lt;/span&gt;mperry&lt;span class="w"&gt; &lt;/span&gt;362K&lt;span class="w"&gt; &lt;/span&gt;Jan&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;11&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;12&lt;/span&gt;:25&lt;span class="w"&gt; &lt;/span&gt;temp_1E-7.topojson
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;OK, so with a simplification tolerance of 1E-10 steridians, we can get a 4.5M file.
If we reduce it to 1E-7, we can get 362k file - a 12.5x reduction. Is the reduction
in file size worth the reduction in geometric accuracy? The only way to find out is to
render maps of the resulting datasets and visually assess them.&lt;/p&gt;
&lt;table&gt;
    &lt;tr&gt;
        &lt;th&gt; &lt;/th&gt;
        &lt;th&gt;Original&lt;/th&gt;
        &lt;th&gt;1E-7&lt;/th&gt;
        &lt;th&gt;1E-8&lt;/th&gt;
        &lt;th&gt;1E-9&lt;/th&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
        &lt;th&gt; &lt;/th&gt;
        &lt;th&gt;&lt;img src="/images/topo_simplify/original.png" width=200&gt;&lt;/th&gt;
        &lt;th&gt;&lt;img src="/images/topo_simplify/1E-7.png" width=200&gt;&lt;/th&gt;
        &lt;th&gt;&lt;img src="/images/topo_simplify/1E-8.png" width=200&gt;&lt;/th&gt;
        &lt;th&gt;&lt;img src="/images/topo_simplify/1E-9.png" width=200&gt;&lt;/th&gt;
    &lt;/tr&gt;
&lt;/table&gt;

&lt;p&gt;First thing that we notice - all of the results have retained topology with no gaps or slivers introduced.
(the key benefit to this workflow).&lt;/p&gt;
&lt;p&gt;Next, we notice that at this scale (roughly 1:500k on my monitor) we can barely
see a difference between the 1E-9 version and the original. And the 1E-7 version
looks a bit too simplified and chunky. So, in this case, we can say that a simplification
tolerance of around 1E-8 steridians is an optimal balance of file size and detail.&lt;/p&gt;
&lt;p&gt;Of course other datasets, scales and uses may have completely different results so please try
it out and let me know how it goes. Just don't settle for simple features simplification next time you need to
reduce file sizes!&lt;/p&gt;</content><category term="articles"></category><category term="GIS"></category><category term="topology"></category><category term="python"></category><category term="bash"></category><category term="GDAL"></category><category term="topojson"></category><category term="geojson"></category></entry><entry><title>Sensitivity Analysis in Python</title><link href="https://www.perrygeo.com/sensitivity-analysis-in-python.html" rel="alternate"></link><published>2014-01-19T00:00:00-07:00</published><updated>2014-01-19T00:00:00-07:00</updated><author><name>Matthew T. Perry</name></author><id>tag:www.perrygeo.com,2014-01-19:/sensitivity-analysis-in-python.html</id><summary type="html">&lt;h3&gt;Demonstrates the use of the &lt;code&gt;SALib&lt;/code&gt; python module to sample and test the sensitivity of models&lt;/h3&gt;
&lt;hr&gt;

&lt;p&gt;As (geo)data scientists, we spend much of our time working with data models that try (with varying degrees of success) to capture some essential truth about the world while still being as simple …&lt;/p&gt;</summary><content type="html">&lt;h3&gt;Demonstrates the use of the &lt;code&gt;SALib&lt;/code&gt; python module to sample and test the sensitivity of models&lt;/h3&gt;
&lt;hr&gt;

&lt;p&gt;As (geo)data scientists, we spend much of our time working with data models that try (with varying degrees of success) to capture some essential truth about the world while still being as simple as possible to provide a useful abstraction. Inevitably, complexity starts to creep into every model and we don't often stop to assess the value added by that complexity. When working with models that require a large number of parameters and a huge domain of potential inputs that are expensive to collect, it becomes difficult to answer the question:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What parameters of the model are the most sensitive?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;In other words, if I am going to spend my resources obtaining/refining data for this model, where should I focus
my efforts in order to get the best bang for the buck? If I spend weeks working on deriving a single parameter for the model,
I want some assurance that the parameter is critically important to the model's prediction. 
The flip-side, of course, is that if a parameter is &lt;em&gt;not&lt;/em&gt; that important to the model's predictive power, I could
save some time by perhaps just using some quick-and-dirty approximation. &lt;/p&gt;
&lt;h3&gt;SALib: a python module for testing model sensitivity&lt;/h3&gt;
&lt;p&gt;I was thrilled to find &lt;a href="http://jdherman.github.io/SALib/"&gt;SALib&lt;/a&gt; which implements a number of vetted methods for quantitatively 
assessing parameter sensitivity. There are three basic steps to running SALib:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Define the parameters to test, define their domain of possible values and generate &lt;em&gt;n&lt;/em&gt; sets of randomized input parameters.   &lt;/li&gt;
&lt;li&gt;Run the model &lt;em&gt;n&lt;/em&gt; times and capture the results.&lt;/li&gt;
&lt;li&gt;Analyze the results to identify the most/least sensitive parameters.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I'll leave the details of these steps to the &lt;a href="http://jdherman.github.io/SALib/"&gt;SALib documentation&lt;/a&gt;.
The beauty of the SALib approach is that you have the flexibility[1] to run any model in any way you want, so long as you can manipulate the inputs and outputs adequately.&lt;/p&gt;
&lt;h3&gt;Case Study: Climate effects on forestry&lt;/h3&gt;
&lt;p&gt;I wanted to compare a forest growth and yield model under different climate change scenarios in order to assess what the most sensitive climate-related variables were. I identified 4 variables:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Climate model (4 global circulation models)&lt;/li&gt;
&lt;li&gt;Representative Concentration Pathways (RCPs; 3 different emission trajectories)&lt;/li&gt;
&lt;li&gt;Mortality factor for species viability (0 to 1)&lt;/li&gt;
&lt;li&gt;Mortality factor for equivalent elevation change (0 to 1)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In this case, I was using the &lt;a href="http://www.fs.fed.us/fmsc/fvs/"&gt;Forest Vegetation Simulator&lt;/a&gt;(FVS) which requires
a configuration file for every model iteration. So, for Step 2, I had to iterate through each set of input variables and use them to generate an appropriate configuration file. This involved translating the real numbers from the samples into categorical variables in some cases. Finally, in order to get the result of the model iteration, I had to parse the outputs of FVS and do some post-processing to obtain the variable of interest (the average volume of standing timber over 100 years). So the flexibility of SALib comes at a slight cost: unless your model works directly with the file formatted for SALib, the input and outputs may require some data manipulation.  &lt;/p&gt;
&lt;p&gt;After running the all required iterations of the model[2] I was able to analyze the results and assess the sensitivity of the four parameters. &lt;/p&gt;
&lt;p&gt;Here's the output of SALib's analysis (formatted slightly for readability):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;Parameter    First_Order First_Order_Conf Total_Order Total_Order_Conf
circulation  0.193685    0.041254         0.477032    0.034803
rcp          0.517451    0.047054         0.783094    0.049091
mortviab    -0.007791    0.006993         0.013050    0.007081
mortelev    -0.005971    0.005510         0.007162    0.006693
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The &lt;em&gt;first order effects&lt;/em&gt; represent the effect of that parameter alone. The &lt;em&gt;total order effects&lt;/em&gt; are arguably more
relevant to understanding the overall interaction of that parameter with your model. The "Conf" columns represent confidence and can be interpreted as error bars.&lt;/p&gt;
&lt;p&gt;In this case, we interpret the output as follows:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;Parameter    Total Order Effect   
circulation  0.47  +- 0.03  (moderate influence)      
rcp          0.78  +- 0.05  (dominant parameter)
mortviab     0.01  +- 0.007 (weak influence)
mortelev     0.007 +- 0.006 (weak influence)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;We can graph each of the input parameters against the results to visualize this:&lt;/p&gt;
&lt;p&gt;&lt;img alt="sagraph" src="/assets/img/sagraph.png"&gt;&lt;/p&gt;
&lt;p&gt;Note that the 'mortelev' component is basically flat (as the factor increases, the result stays the same) whereas the choice of 'rcp' has a heavy influence (as emissions increase to the highest level, the resulting prediction for timber volumes are noticeably decreased).&lt;/p&gt;
&lt;p&gt;The conclusion is that the climate variables, particularly the RCPs related to human-caused emissions, were the strongest determinants[1] of tree growth &lt;em&gt;for this particular forest stand&lt;/em&gt;. This ran counter to our initial intuition that the mortality factors would play a large role in the model. Based on this sensitivity analysis, we may be able to avoid wasting effort on refining parameters that are of minor consequence to the output.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Footnotes:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Compared to more tightly integrated, model-specific methods of sensitivity analysis&lt;/li&gt;
&lt;li&gt;20 thousand iterations took approximately 8 hours; sensitivity analysis generally requires lots of processing&lt;/li&gt;
&lt;li&gt;Note that the influence of a parameter says nothing about direct &lt;em&gt;causality&lt;/em&gt;&lt;/li&gt;
&lt;/ol&gt;</content><category term="Data Science"></category></entry><entry><title>Leaflet SimpleCSV</title><link href="https://www.perrygeo.com/leaflet-simplecsv.html" rel="alternate"></link><published>2013-09-30T00:00:00-06:00</published><updated>2013-09-30T00:00:00-06:00</updated><author><name>Matthew T. Perry</name></author><id>tag:www.perrygeo.com,2013-09-30:/leaflet-simplecsv.html</id><summary type="html">&lt;h3&gt;Simple leaftlet-based template for mapping tabular point data on a slippy map&lt;/h3&gt;
&lt;hr&gt;

&lt;p&gt;Anyone who's worked with spatial data and the web has run across the need to take 
some simple tabular data and put points on an interactive map. 
It's the fundamental "&lt;em&gt;Hello World&lt;/em&gt;" of web mapping. Yet I always …&lt;/p&gt;</summary><content type="html">&lt;h3&gt;Simple leaftlet-based template for mapping tabular point data on a slippy map&lt;/h3&gt;
&lt;hr&gt;

&lt;p&gt;Anyone who's worked with spatial data and the web has run across the need to take 
some simple tabular data and put points on an interactive map. 
It's the fundamental "&lt;em&gt;Hello World&lt;/em&gt;" of web mapping. Yet I always find myself spending way too much time
solving this seemingly simple problem. When you consider zoom levels, attributes,
interactivity, clustering, querying, etc... it becomes apparent that interactive maps
require a bit more legwork. But that functionality is fairly consistent case-to-case so I've developed a generalized solution that works for the majority of basic use cases out there: &lt;/p&gt;
&lt;p&gt;&lt;a class="btn btn-primary" href="https://github.com/perrygeo/leaflet-simple-csv"&gt;leaftlet-simple-csv on github&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The idea is pretty generic but useful for most point marker maps:
* Data is in tabular delimited-text (csv, etc.) with two required columns: &lt;code&gt;lat&lt;/code&gt; and &lt;code&gt;lng&lt;/code&gt;
* Points are plotted on full-screen &lt;a href="https://github.com/Leaflet/Leaflet"&gt;Leaflet&lt;/a&gt; map
* Point markers are clustered dynamically based on zoom level.
* Clicking on a point cluster will zoom into the extent of the underlying features.
* Hovering on the point will display the name. 
* Clicking will display a popup with columns/properties displayed as an html table.
* Full text filtering with typeahead
* Completely client-side javascript with all dependencies included or linked via CDN&lt;/p&gt;
&lt;p&gt;Of course this is mostly just a packaged version of existing work, namely &lt;a href="https://github.com/Leaflet/Leaflet"&gt;Leaflet&lt;/a&gt; with the &lt;a href="https://github.com/joker-x/Leaflet.geoCSV"&gt;geoCSV&lt;/a&gt; and &lt;a href="https://github.com/Leaflet/Leaflet.markercluster"&gt;markercluster&lt;/a&gt; plugins.&lt;/p&gt;
&lt;h2&gt;Usage&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Grab the &lt;a href="https://github.com/perrygeo/leaflet-simple-csv/archive/master.zip"&gt;leaflet-simple-csv zip file&lt;/a&gt; and unzip it to a location accessible through a web server. &lt;/li&gt;
&lt;li&gt;Copy the &lt;code&gt;config.js.template&lt;/code&gt; to &lt;code&gt;config.js&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Visit the &lt;a href="assets/leaflet-simple-csv/index.html"&gt;index.html&lt;/a&gt; page to confirm everything is working with the built-in example.&lt;/li&gt;
&lt;li&gt;Customize your &lt;code&gt;config.js&lt;/code&gt; for your dataset.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;An example config:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;var&lt;span class="w"&gt; &lt;/span&gt;dataUrl&lt;span class="w"&gt; &lt;/span&gt;=&lt;span class="w"&gt; &lt;/span&gt;&amp;#39;data/data.csv&amp;#39;;
var&lt;span class="w"&gt; &lt;/span&gt;maxZoom&lt;span class="w"&gt; &lt;/span&gt;=&lt;span class="w"&gt; &lt;/span&gt;9;
var&lt;span class="w"&gt; &lt;/span&gt;fieldSeparator&lt;span class="w"&gt; &lt;/span&gt;=&lt;span class="w"&gt; &lt;/span&gt;&amp;#39;|&amp;#39;;
var&lt;span class="w"&gt; &lt;/span&gt;baseUrl&lt;span class="w"&gt; &lt;/span&gt;=&lt;span class="w"&gt; &lt;/span&gt;&amp;#39;http://otile{s}.mqcdn.com/tiles/1.0.0/osm/{z}/{x}/{y}.jpg&amp;#39;;
var&lt;span class="w"&gt; &lt;/span&gt;baseAttribution&lt;span class="w"&gt; &lt;/span&gt;=&lt;span class="w"&gt; &lt;/span&gt;&amp;#39;Data,&lt;span class="w"&gt; &lt;/span&gt;imagery&lt;span class="w"&gt; &lt;/span&gt;and&lt;span class="w"&gt; &lt;/span&gt;map&lt;span class="w"&gt; &lt;/span&gt;information&lt;span class="w"&gt; &lt;/span&gt;provided&lt;span class="w"&gt; &lt;/span&gt;by&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;http://open.mapquest.co.uk&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;target=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;_blank&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;MapQuest&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;http://www.openstreetmap.org/&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;target=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;_blank&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;OpenStreetMap&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;and&lt;span class="w"&gt; &lt;/span&gt;contributors,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;http://creativecommons.org/licenses/by-sa/2.0/&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;target=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;_blank&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;CC-BY-SA&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;&amp;#39;;
var&lt;span class="w"&gt; &lt;/span&gt;subdomains&lt;span class="w"&gt; &lt;/span&gt;=&lt;span class="w"&gt; &lt;/span&gt;&amp;#39;1234&amp;#39;;
var&lt;span class="w"&gt; &lt;/span&gt;clusterOptions&lt;span class="w"&gt; &lt;/span&gt;=&lt;span class="w"&gt; &lt;/span&gt;{showCoverageOnHover:&lt;span class="w"&gt; &lt;/span&gt;false,&lt;span class="w"&gt; &lt;/span&gt;maxClusterRadius:&lt;span class="w"&gt; &lt;/span&gt;50};
var&lt;span class="w"&gt; &lt;/span&gt;labelColumn&lt;span class="w"&gt; &lt;/span&gt;=&lt;span class="w"&gt; &lt;/span&gt;&amp;quot;Name&amp;quot;;
var&lt;span class="w"&gt; &lt;/span&gt;opacity&lt;span class="w"&gt; &lt;/span&gt;=&lt;span class="w"&gt; &lt;/span&gt;1.0;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The example dataset:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;Country|Name|lat|lng|Altitude
United States|New York City|40.7142691|-74.0059738|2.0
United States|Los Angeles|34.0522342|-118.2436829|115.0
United States|Chicago|41.8500330|-87.6500549|181.0
United States|Houston|29.7632836|-95.3632736|15.0
...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;I make no claims that this is the "right" way to do it but leveraging
100% client-side javascript libraries and native delimited-text formats seems like the simplest approach. 
Many of the features included here (clustering, filtering) are useful enough
to apply to most situations and hopefully you'll find it useful.&lt;/p&gt;
&lt;hr&gt;
&lt;div&gt;&lt;iframe src="http://blog.perrygeo.net/assets/leaflet-simple-csv/index.html" height="450" width="740"&gt;&lt;/iframe&gt;&lt;/div&gt;</content><category term="GIS"></category></entry><entry><title>Python rasterstats</title><link href="https://www.perrygeo.com/python-rasterstats.html" rel="alternate"></link><published>2013-09-24T00:00:00-06:00</published><updated>2013-09-24T00:00:00-06:00</updated><author><name>Matthew T. Perry</name></author><id>tag:www.perrygeo.com,2013-09-24:/python-rasterstats.html</id><summary type="html">&lt;h3&gt;This article introduces a python module for summarizing geospatial raster datasets based on vector geometries (i.e. zonal statistics).&lt;/h3&gt;
&lt;p&gt;A common task in many of my data workflows involves "zonal statistics";  summarizing raster data based on vector geometries. Despite many
alternatives (starspan, the QGIS Zonal Statistics plugin, ArcPy and R …&lt;/p&gt;</summary><content type="html">&lt;h3&gt;This article introduces a python module for summarizing geospatial raster datasets based on vector geometries (i.e. zonal statistics).&lt;/h3&gt;
&lt;p&gt;A common task in many of my data workflows involves "zonal statistics";  summarizing raster data based on vector geometries. Despite many
alternatives (starspan, the QGIS Zonal Statistics plugin, ArcPy and R) there
were none that were&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;open source&lt;/li&gt;
&lt;li&gt;fast enough&lt;/li&gt;
&lt;li&gt;flexible enough&lt;/li&gt;
&lt;li&gt;worked with python data structures&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We'd written a wrapper around starspan for madrona (see &lt;a href="https://github.com/Ecotrust/madrona/blob/master/docs/raster_stats.rst"&gt;madrona.raster_stats&lt;/a&gt; ) but
relying on shell calls and an aging, unmaintained C++ code base was not cutting
it.&lt;/p&gt;
&lt;p&gt;So I set out to create a solution using numpy, GDAL and python. The
&lt;code&gt;rasterstats&lt;/code&gt; package was born. &lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/perrygeo/python-raster-stats" class="btn btn-primary"&gt;`python-raster-stats` on github&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;Example&lt;/h2&gt;
&lt;p&gt;Let's jump into an example. I've got a polygon shapefile of continental US
&lt;em&gt;state boundaries&lt;/em&gt; and a raster dataset of &lt;em&gt;annual precipitation&lt;/em&gt; from the
&lt;a href="http://www.cec.org/Page.asp?PageID=924&amp;amp;ContentID=2336"&gt;North American Environmental
Atlas&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img alt="states_precip" src="/assets/img/states_precip.jpeg"&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;states&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;data/boundaries_contus.shp&amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;precip&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;data/precipitation.tif&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;raster_stats&lt;/code&gt; function is the main entry point. Provide a vector and a
raster as input and expect a list of dicts, one for each input feature.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;rasterstats&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;raster_stats&lt;/span&gt;
&lt;span class="n"&gt;rain_stats&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;raster_stats&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;states&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;precip&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stats&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;*&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;copy_properties&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rain_stats&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# continental US; 48 states plus District of Columbia&lt;/span&gt;

&lt;span class="mi"&gt;49&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Print out the stats for a given state:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;rain_stats&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;NAME&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Oregon&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;COUNTRY&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;USA&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="s1"&gt;&amp;#39;EDIT&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;NEW&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="s1"&gt;&amp;#39;EDIT_DATE&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;20060803&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="s1"&gt;&amp;#39;NAME&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Oregon&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="s1"&gt;&amp;#39;STATEABB&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;US-OR&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="s1"&gt;&amp;#39;Shape_Area&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;250563567264.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="s1"&gt;&amp;#39;Shape_Leng&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;2366783.00361&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="s1"&gt;&amp;#39;UIDENT&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;124704&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="s1"&gt;&amp;#39;__fid__&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;35&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="s1"&gt;&amp;#39;count&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;250510&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="s1"&gt;&amp;#39;majority&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;263&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="s1"&gt;&amp;#39;max&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;3193.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="s1"&gt;&amp;#39;mean&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;779.2223903237395&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="s1"&gt;&amp;#39;median&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;461.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="s1"&gt;&amp;#39;min&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;205.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="s1"&gt;&amp;#39;minority&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3193&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="s1"&gt;&amp;#39;range&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;2988.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="s1"&gt;&amp;#39;std&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;631.539502512283&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="s1"&gt;&amp;#39;sum&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;195203001.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="s1"&gt;&amp;#39;unique&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2865&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Find the three driest states:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;NAME&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;mean&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; 
       &lt;span class="nb"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rain_stats&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;mean&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;])[:&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;


&lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Nevada&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;248.23814034118908&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
 &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Utah&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;317.668743027571&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
 &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Arizona&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;320.6157232064074&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;And write the data out to a csv.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;rasterstats&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;stats_to_csv&lt;/span&gt;
&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;out.csv&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;w&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;fh&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;fh&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stats_to_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rain_stats&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Geo interface&lt;/h2&gt;
&lt;p&gt;The basic usage above shows the path of an entire OGR vector layer as the first argument. But raster-stats
also supports other vector features/geometries.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Well-Known Text/Binary&lt;/li&gt;
&lt;li&gt;GeoJSON string and mappings&lt;/li&gt;
&lt;li&gt;Any python object that supports the &lt;a href="https://gist.github.com/sgillies/2217756"&gt;geo_interface&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Single objects or iterables&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In this example, I use a geojson-like python mapping to specify a single geometry&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;geom&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;coordinates&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[[&lt;/span&gt;
   &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;594335.108537269&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;570957.932799394&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
   &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;422374.54395311&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;593387.5716581973&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
   &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;444804.1828119133&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;765348.1362423564&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
   &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;631717.839968608&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;735441.9510972851&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
   &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;594335.108537269&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;570957.932799394&lt;/span&gt;&lt;span class="p"&gt;]]],&lt;/span&gt;
 &lt;span class="s1"&gt;&amp;#39;type&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Polygon&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;raster_stats&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;geom&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;precip&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stats&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;min median max&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;__fid__&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;max&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;1011.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;median&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;451.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;min&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;229.0&lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Categorical&lt;/h2&gt;
&lt;p&gt;We're not limited to descriptive statistics for &lt;em&gt;continuous&lt;/em&gt; rasters either; we
can get unique pixel counts for &lt;em&gt;categorical&lt;/em&gt; rasters as well. In this example,
we've got a raster of 2005 land cover (i.e. general vegetation type). &lt;/p&gt;
&lt;p&gt;&lt;img alt="states_veg" src="/assets/img/states_veg.jpeg"&gt;&lt;/p&gt;
&lt;p&gt;Note that
we can specify only the stats that make sense and the &lt;code&gt;categorical=True&lt;/code&gt;
provides a count of each pixel value.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;landcover&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;/data/workspace/rasterstats_blog/NA_LandCover_2005.img&amp;quot;&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;veg_stats&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;raster_stats&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;states&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;landcover&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="n"&gt;stats&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;count majority minority unique&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;copy_properties&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;nodata_value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;categorical&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;veg_stats&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;NAME&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Oregon&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;999956&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3005&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;198535&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2270805&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;126199&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;20883&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;301884&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;17452&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="mi"&gt;17&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;39246&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="mi"&gt;18&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;28872&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="mi"&gt;19&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2174&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="s1"&gt;&amp;#39;COUNTRY&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;USA&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="s1"&gt;&amp;#39;EDIT&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;NEW&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="s1"&gt;&amp;#39;EDIT_DATE&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;20060803&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="s1"&gt;&amp;#39;NAME&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Oregon&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="s1"&gt;&amp;#39;STATEABB&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;US-OR&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="s1"&gt;&amp;#39;Shape_Area&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;250563567264.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="s1"&gt;&amp;#39;Shape_Leng&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;2366783.00361&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="s1"&gt;&amp;#39;UIDENT&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;124704&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="s1"&gt;&amp;#39;__fid__&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;35&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="s1"&gt;&amp;#39;count&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4009017&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="s1"&gt;&amp;#39;majority&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="s1"&gt;&amp;#39;minority&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="s1"&gt;&amp;#39;unique&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Of course the pixel values alone don't make much sense. We need to interpret the
pixel values as land cover classes:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;Value, Class_name
1       Temperate or sub-polar needleleaf forest
2       Sub-polar taiga needleleaf forest
3       Tropical or sub-tropical broadleaf evergreen
4       Tropical or sub-tropical broadleaf deciduous
5       Temperate or sub-polar broadleaf deciduous
6        Mixed Forest
7       Tropical or sub-tropical shrubland
8       Temperate or sub-polar shrubland
9       Tropical or sub-tropical grassland
10      Temperate or sub-polar grassland
11      Sub-polar or polar shrubland-lichen-moss
12      Sub-polar or polar grassland-lichen-moss
13      Sub-polar or polar barren-lichen-moss
14      Wetland
15      Cropland
16      Barren Lands
17      Urban and Built-up
18      Water
19      Snow and Ice
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;So, for our Oregon example above we can see that, despite Oregon's reputation as
a lush green landscape, the majority land cover class (#8) is "Temperate or sub-
polar shrubland" at 2.27m pixels out of 4 millions total.&lt;/p&gt;
&lt;p&gt;There's a lot more functionality that isn't covered in this post but you get the 
picture... please check it out and let me know what you think. &lt;/p&gt;</content><category term="GIS"></category></entry><entry><title>Creating UTFGrids directly from a polygon datasource</title><link href="https://www.perrygeo.com/creating-utfgrids-directly-from-a-polygon-datasource.html" rel="alternate"></link><published>2012-08-20T00:00:00-06:00</published><updated>2012-08-20T00:00:00-06:00</updated><author><name>Matthew T. Perry</name></author><id>tag:www.perrygeo.com,2012-08-20:/creating-utfgrids-directly-from-a-polygon-datasource.html</id><summary type="html">&lt;p&gt;We've begun to rely on the interactivity provided by &lt;a href="http://mapbox.com/mbtiles-spec/utfgrid/"&gt;UTFGrids&lt;/a&gt; in many of our recent web maps. (Quick recap: UTFGrids are "invisible" map tiles that allow direct interactivity with feature attributes without querying the server.) Earlier this year, I created the &lt;a href="/2012/02/24/utfgrids-with-openlayers-and-tilestache/"&gt;initial OpenLayers UTFGrid support&lt;/a&gt; and was glad to see …&lt;/p&gt;</summary><content type="html">&lt;p&gt;We've begun to rely on the interactivity provided by &lt;a href="http://mapbox.com/mbtiles-spec/utfgrid/"&gt;UTFGrids&lt;/a&gt; in many of our recent web maps. (Quick recap: UTFGrids are "invisible" map tiles that allow direct interactivity with feature attributes without querying the server.) Earlier this year, I created the &lt;a href="/2012/02/24/utfgrids-with-openlayers-and-tilestache/"&gt;initial OpenLayers UTFGrid support&lt;/a&gt; and was glad to see it accepted into OpenLayer 2.12 (with some enhancements). &lt;/p&gt;
&lt;p&gt;With the client-side javascript support in place, the only missing piece in the workflow was to create the UTFGrid .json files. 
We had expirimented with several alternate &lt;a href="https://github.com/springmeyer/utfgrid-example-writers"&gt;UTFGrid renderers&lt;/a&gt; but Mapnik's rendering was by far the fastest and produced the best results. 
Using Tilemill was a convenient way to leverage the Mapnik UTFGrid renderer but it came at the cost of a somewhat circuitious and manual workflow: &lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Load the data up into &lt;a href="http://mapbox.com/tilemill/"&gt;Tilemill&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;Configure interactivity fields&lt;/li&gt;
&lt;li&gt;Export to .mbtiles&lt;/li&gt;
&lt;li&gt;&lt;a href="http://blog.perrygeo.net/2012/03/25/working-with-mbtiles-in-python/"&gt;Convert to .json files&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;What we really needed was a &lt;strong&gt;script to take a polygon shapefile and render the UTFGrids directly to files&lt;/strong&gt;. &lt;a href="http://mapnik.org"&gt;Mapnik&lt;/a&gt; would provide the rendering while the &lt;a href="http://www.maptiler.org/google-maps-coordinates-tile-bounds-projection/globalmaptiles.py"&gt;Global Map Tiles&lt;/a&gt; python module would provide the logic for going back and forth between geographic coordinates and tile grid coordinates. From there it's just a matter of determining the extent of the data set and, for a specified set of zoom levels, looping through and using Mapnik to render the UTFGrid to a .json file in &lt;code&gt;Z/X/Y.json&lt;/code&gt; directory structure.  &lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/Ecotrust/create-utfgrids" class="btn btn-primary"&gt;Get `create-utfgrids` on github&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If we have a mercator polygon shapefile of ecoregions and want to render UTFGrids for zoom levels 3 through 5 using the &lt;code&gt;dom_desc&lt;/code&gt; and &lt;code&gt;div_desc&lt;/code&gt; attributes, we could use a command like&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;./create_utfgrids.py&lt;span class="w"&gt; &lt;/span&gt;test_data/bailey_merc.shp&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;ecoregions&lt;span class="w"&gt; &lt;/span&gt;-f&lt;span class="w"&gt; &lt;/span&gt;dom_desc,div_desc

WARNING:
This&lt;span class="w"&gt; &lt;/span&gt;script&lt;span class="w"&gt; &lt;/span&gt;assumes&lt;span class="w"&gt; &lt;/span&gt;a&lt;span class="w"&gt; &lt;/span&gt;polygon&lt;span class="w"&gt; &lt;/span&gt;shapefile&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;spherical&lt;span class="w"&gt; &lt;/span&gt;mercator&lt;span class="w"&gt; &lt;/span&gt;projection.
If&lt;span class="w"&gt; &lt;/span&gt;any&lt;span class="w"&gt; &lt;/span&gt;of&lt;span class="w"&gt; &lt;/span&gt;these&lt;span class="w"&gt; &lt;/span&gt;assumptions&lt;span class="w"&gt; &lt;/span&gt;are&lt;span class="w"&gt; &lt;/span&gt;not&lt;span class="w"&gt; &lt;/span&gt;true,&lt;span class="w"&gt; &lt;/span&gt;don&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;t&lt;span class="w"&gt; &lt;/span&gt;count&lt;span class="w"&gt; &lt;/span&gt;on&lt;span class="w"&gt; &lt;/span&gt;the&lt;span class="w"&gt; &lt;/span&gt;results!
&lt;span class="w"&gt; &lt;/span&gt;*&lt;span class="w"&gt; &lt;/span&gt;Processing&lt;span class="w"&gt; &lt;/span&gt;Zoom&lt;span class="w"&gt; &lt;/span&gt;Level&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;*&lt;span class="w"&gt; &lt;/span&gt;Processing&lt;span class="w"&gt; &lt;/span&gt;Zoom&lt;span class="w"&gt; &lt;/span&gt;Level&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;*&lt;span class="w"&gt; &lt;/span&gt;Processing&lt;span class="w"&gt; &lt;/span&gt;Zoom&lt;span class="w"&gt; &lt;/span&gt;Level&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;and inspect the output (e.g. zoom level 5, X=20, Y=18)&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;cat&lt;span class="w"&gt; &lt;/span&gt;ecoregions/5/20/18.json&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;python&lt;span class="w"&gt; &lt;/span&gt;-mjson.tool
&lt;span class="o"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;data&amp;quot;&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;192&amp;quot;&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;div_desc&amp;quot;&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;RAINFOREST REGIME MOUNTAINS&amp;quot;&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;dom_desc&amp;quot;&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;HUMID TROPICAL DOMAIN&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;
...
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;grid&amp;quot;&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;  !!!!!!!!!#####&lt;/span&gt;$&lt;span class="s2"&gt;%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%&amp;quot;&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;
...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Some caveats:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;This currently only works for polygon datasets in a Web Mercator projection.&lt;/li&gt;
&lt;li&gt;It's only tested with shapefiles as it assumes a single-layer datasource at the moment. Full OGR Datasource support would not be too difficult to add for PostGIS, etc.&lt;/li&gt;
&lt;li&gt;It assumes a top-origin tile scheme (as do OSM and Google Maps). Supporting TMS bottom-origin schemes in the future should be straightforward. &lt;/li&gt;
&lt;li&gt;Requires OGR and Mapnik &amp;gt;= 2.0 with python bindings. Finding windows binaries for the required version of Mapnik may be difficult so using OSX/Linux is recommended at this time. &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Many thanks to Dane Springmeyer for his help on UTFGrid related matters and 
and to  Klokan Petr Přidal for his &lt;a href="http://www.maptiler.org/google-maps-coordinates-tile-bounds-projection/"&gt;MapTiler docs&lt;/a&gt;&lt;/p&gt;</content><category term="articles"></category><category term="python"></category><category term="utfgrid"></category><category term="mapnik"></category></entry><entry><title>Introducing the Madrona framework</title><link href="https://www.perrygeo.com/introducing-the-madrona-framework.html" rel="alternate"></link><published>2012-07-11T00:00:00-06:00</published><updated>2012-07-11T00:00:00-06:00</updated><author><name>Matthew T. Perry</name></author><id>tag:www.perrygeo.com,2012-07-11:/introducing-the-madrona-framework.html</id><summary type="html">&lt;h3&gt;&lt;a href="http://madrona.ecotrust.org"&gt;Madrona&lt;/a&gt;: A software framework for effective place-based decision making&lt;/h3&gt;
&lt;p&gt;&lt;img alt="Madrona" src="http://madrona.ecotrust.org/assets/img/madrona-logo.png"&gt;&lt;/p&gt;
&lt;p&gt;My work at &lt;a href="http://www.ecotrust.org/"&gt;Ecotrust&lt;/a&gt; mainly revolves around creating web-based spatial analysis tools - software to bring data-driven science to the place-based descision making process. This began several years ago when I joined the MarineMap team. Since working with Ecotrust, we've taken the …&lt;/p&gt;</summary><content type="html">&lt;h3&gt;&lt;a href="http://madrona.ecotrust.org"&gt;Madrona&lt;/a&gt;: A software framework for effective place-based decision making&lt;/h3&gt;
&lt;p&gt;&lt;img alt="Madrona" src="http://madrona.ecotrust.org/assets/img/madrona-logo.png"&gt;&lt;/p&gt;
&lt;p&gt;My work at &lt;a href="http://www.ecotrust.org/"&gt;Ecotrust&lt;/a&gt; mainly revolves around creating web-based spatial analysis tools - software to bring data-driven science to the place-based descision making process. This began several years ago when I joined the MarineMap team. Since working with Ecotrust, we've taken the MarineMap software far beyond it's original niche. What was once a specific tool for marine protected area planning has now become a powerful framework for &lt;a href="http://madrona.ecotrust.org/experience/"&gt;all sorts of web-based spatial tools&lt;/a&gt; in the realms of marine, forestry, conservation planning, aquatic habitat restoration, etc. So, in a sense, &lt;a href="http://madrona.ecotrust.org"&gt;Madrona&lt;/a&gt; is a recognition of that evolution. &lt;/p&gt;
&lt;p&gt;From the official &lt;a href="http://madrona.ecotrust.org"&gt;Madrona&lt;/a&gt; release announcement from the &lt;a href="http://blog.ecotrust.org/software-for-21st-century-decisions-2/"&gt;Ecotrust blog post&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Over the last year we’ve distilled the best ideas from our most successful tools into a suite of software building blocks that can be mixed and matched to create cutting-edge software for decision support and spatial planning at any scale. These building blocks are already at the heart of our work and now we’re ready to share them with you.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;So what is &lt;a href="http://madrona.ecotrust.org"&gt;Madrona&lt;/a&gt; from a developer's perspective? &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A set of &lt;em&gt;python&lt;/em&gt; &lt;em&gt;django&lt;/em&gt; apps that provide models, views and templates for representing spatial features and solving problems specific to spatial decision tools.&lt;/li&gt;
&lt;li&gt;A RESTful &lt;em&gt;API&lt;/em&gt; for accessing spatial features&lt;/li&gt;
&lt;li&gt;A collection of &lt;em&gt;javascript&lt;/em&gt; libraries (based on JQuery) to provide a web-based interface to the API.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In short, we think its a great platform for spatial tools and we want to open it up to the wider developer audience. Ecotrust already has many &lt;a href="http://madrona.ecotrust.org/experience/"&gt;madrona-based apps&lt;/a&gt; in the wild (with many more in development) but we're hoping to get other folks using (and contributing to) the Madrona framework in the future. &lt;/p&gt;
&lt;p&gt;I know this post is short on technical details but there will more to come ... for now, check out the &lt;a href="http://madrona.ecotrust.org/technology/"&gt;technology page&lt;/a&gt; for an overview or the &lt;a href="http://madrona.ecotrust.org/developer/"&gt;developer's page&lt;/a&gt; to dive in. &lt;/p&gt;</content><category term="articles"></category><category term="python"></category><category term="madrona"></category><category term="ecotrust"></category><category term="marinemap"></category></entry><entry><title>Migrating from Wordpress to Jekyll</title><link href="https://www.perrygeo.com/migrating-from-wordpress-to-jekyll.html" rel="alternate"></link><published>2012-04-28T00:00:00-06:00</published><updated>2012-04-28T00:00:00-06:00</updated><author><name>Matthew T. Perry</name></author><id>tag:www.perrygeo.com,2012-04-28:/migrating-from-wordpress-to-jekyll.html</id><summary type="html">&lt;p&gt;I just switched this blog from an ancient version of wordpress running on a VPS 
to a static-file &lt;a href="http://jekyllbootstrap.com/"&gt;jekyll bootstrap&lt;/a&gt; site 
(hosted by &lt;a href="http://github.com/perrygeo/perrygeo.github.com"&gt;github&lt;/a&gt;). 
Let me know if you experience any wierdness on the site or feeds. I've taken good measures to make sure links don't break (old URLS should …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I just switched this blog from an ancient version of wordpress running on a VPS 
to a static-file &lt;a href="http://jekyllbootstrap.com/"&gt;jekyll bootstrap&lt;/a&gt; site 
(hosted by &lt;a href="http://github.com/perrygeo/perrygeo.github.com"&gt;github&lt;/a&gt;). 
Let me know if you experience any wierdness on the site or feeds. I've taken good measures to make sure links don't break (old URLS should get a 301 permanent redirect to blog.perrygeo.net) but let me know if you get any 404s.&lt;/p&gt;
&lt;h3&gt;So why do it?&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;Having a PHP-MySQL app running on a VPS just to serve up a bunch of blog posts seemed excessive. I don't have the desire to maintain that sort of infrastructure for a simple blog!&lt;/li&gt;
&lt;li&gt;Wordpress' editing and admin interface suck. I prefer vim and bash.&lt;/li&gt;
&lt;li&gt;Markdown is a great language for quickly banging out blog posts.&lt;/li&gt;
&lt;li&gt;Static files just make sense for what is basically static content.&lt;/li&gt;
&lt;li&gt;Github pages provides the hosting for me and even handles CNAMEs for DNS.&lt;/li&gt;
&lt;li&gt;Managing revisions with &lt;code&gt;git&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;The conversion process&lt;/h3&gt;
&lt;p&gt;It was not an entirely smooth transition, most of which can be traced directly to dumb decisions on my part. I won't recount the entire process (there are plenty of guides on internets) but I'll outline the major steps here:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Export the wordpress blog to an xml file. I has to use &lt;code&gt;xmllint&lt;/code&gt; to clean it up a bit. &lt;/li&gt;
&lt;li&gt;Set up a &lt;a href="http://disqus.com"&gt;disqus&lt;/a&gt; account and import my wordpress file. Disqus will handle all the comments which are the only dynamic content on the page. &lt;/li&gt;
&lt;li&gt;Use &lt;a href="https://github.com/thomasf/exitwp"&gt;exitwp.py&lt;/a&gt; to convert the xml to jekyll markdown files. This worked OK. Not great. Tags and formatting did not come through as expected and I had to wrestle the script a bit. Tables were destroyed and some iframes (youtube links) were lost. &lt;/li&gt;
&lt;li&gt;Forked Jekyll Bootstrap and brought in my posts. &lt;/li&gt;
&lt;li&gt;Started tweaking of css and markdown to get formatting right. Still have a ways to go on this front - let me know if there is any content you'd like me to restore faster than others.&lt;/li&gt;
&lt;li&gt;Had to write a little web service to redirect posts; the old blog stupidly used the default wordpress URLS like &lt;code&gt;/wordpress/?p=4&lt;/code&gt; which needed to go to &lt;code&gt;/2010/01/01/blah&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;My images were all over the place; some I had in wordpress uploads, others on various servers, some were absolute links, others relative. Gathering them all in one place and using some sed-fu to get the paths right was essential.&lt;/li&gt;
&lt;li&gt;Retagged some posts - still working on tags.&lt;/li&gt;
&lt;li&gt;Set up Google Analytics to track usage. &lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I think that's about it. There are still some big formatting problems on older posts (mostly due to the fact that I used blockquotes for code). And tables are still destroyed. I'll be working on cleaning these up as I go along. &lt;/p&gt;
&lt;p&gt;Overall impression of Jekyll-Bootstrap and hosting with Github pages? &lt;strong&gt;Awesome&lt;/strong&gt;. I would highly recomend it to anyone starting a new blog or converting a smaller/better-behaved wordpress site. 
It is so much better than having to deal with PHP and MySQL (hopefully the last time I'll ever see them!). But the conversion was a bit tricky and took way more of my Friday and Saturday than I'd like to admit. I would not want to do that again... But I'm glad did. &lt;/p&gt;
&lt;p&gt;What do you think of the new digs?&lt;/p&gt;</content><category term="articles"></category><category term="jekyll"></category><category term="git"></category></entry><entry><title>Working with mbtiles in python</title><link href="https://www.perrygeo.com/working-with-mbtiles-in-python.html" rel="alternate"></link><published>2012-03-25T00:00:00-06:00</published><updated>2012-03-25T00:00:00-06:00</updated><author><name>Matthew T. Perry</name></author><id>tag:www.perrygeo.com,2012-03-25:/working-with-mbtiles-in-python.html</id><summary type="html">&lt;p&gt;&lt;a href="https://github.com/perrygeo/python-mbtiles"&gt;python-mbtiles&lt;/a&gt;. Check it out.&lt;/p&gt;
&lt;p&gt;I've been working a bit with Tilemill lately and love the Carto css styling, iteractivity through UTFGrids and being able to export the whole deal as a single &lt;a href="http://mapbox.com/mbtiles-spec/"&gt;mbtiles&lt;/a&gt; sqlite database. But when it comes to working with the mbtiles databases, I've found both Tilestache and …&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;a href="https://github.com/perrygeo/python-mbtiles"&gt;python-mbtiles&lt;/a&gt;. Check it out.&lt;/p&gt;
&lt;p&gt;I've been working a bit with Tilemill lately and love the Carto css styling, iteractivity through UTFGrids and being able to export the whole deal as a single &lt;a href="http://mapbox.com/mbtiles-spec/"&gt;mbtiles&lt;/a&gt; sqlite database. But when it comes to working with the mbtiles databases, I've found both Tilestache and Tilestream to be fairly limiting:&lt;/p&gt;
&lt;p&gt;Tilestache serves images but does not (yet) serve up UTFGrids _directly from mbtiles _ while Tilestream hardcodes a "grid()" JSONP callback around the returned json data making it fairly specific to Wax client libraries.&lt;/p&gt;
&lt;p&gt;So I went down two paths, first trying to export all the tiles out of mbtiles to json and png files (for those times when you just want to serve static files), then trying to write a simple server that would do dynamic jsonp callbacks. Turns out that in the process, I was able to abstract a lot of the python&amp;lt; -&amp;gt;sqlite interaction into some generic python classes.&lt;/p&gt;
&lt;p&gt;Thus &lt;a href="https://github.com/perrygeo/python-mbtiles"&gt;python-mbtiles&lt;/a&gt; was born. It provides a simple mbtiles web server, a conversion script, and some python classes to work with. No frills, no anything really at this point. More an experiment gone right that might be useful to someone out there in GeoPython land.  Enjoy and let me know if you have any ideas!&lt;/p&gt;</content><category term="articles"></category><category term="mbtiles"></category><category term="utfgrid"></category><category term="tilestream"></category><category term="python"></category><category term="tornado"></category></entry><entry><title>Average Aspect</title><link href="https://www.perrygeo.com/average-aspect.html" rel="alternate"></link><published>2012-03-18T00:00:00-06:00</published><updated>2012-03-18T00:00:00-06:00</updated><author><name>Matthew T. Perry</name></author><id>tag:www.perrygeo.com,2012-03-18:/average-aspect.html</id><summary type="html">&lt;p&gt;Ever try to figure out what the average aspect of an area is? i.e. &lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;What direction does this hillside face? &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Let's say we want to determine the average elevation of an area based on a raster DEM. Just take the arithmetic mean of all the elevation cells contained in …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Ever try to figure out what the average aspect of an area is? i.e. &lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;What direction does this hillside face? &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Let's say we want to determine the average elevation of an area based on a raster DEM. Just take the arithmetic mean of all the elevation cells contained in the area - a simple zonal statistics problem.&lt;/p&gt;
&lt;p&gt;Turns out that aspect is not quite as straightforward. True, we can easily use &lt;a href="http://www.gdal.org/gdaldem.html"&gt;gdaldem&lt;/a&gt; to create an aspect map.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;gdaldem aspect elevation.tif aspect.tif&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;This gives a raster with values in degrees: 0 is north, 90 is east, 180 is south, etc... but note that 360 is north as well.  We're dealing with angular units, not linear units. &lt;/p&gt;
&lt;p&gt;For example, take a nearly North facing hillside; the left edge is facing slightly NW (350 degrees) while the right edge faces slighty NE (10 degrees).&lt;/p&gt;
&lt;p&gt;The arithmetic mean of the aspect values = &lt;code&gt;(350+350+10+10)/4 = 180°&lt;/code&gt;. Due south? That's entirely wrong! It doesn't take into account the angular units. For that we need to create grids representing the &lt;em&gt;sin&lt;/em&gt; and &lt;em&gt;cos&lt;/em&gt; of the aspect. &lt;/p&gt;
&lt;p&gt;Luckily you can use the handy &lt;a href="http://svn.osgeo.org/gdal/trunk/gdal/swig/python/scripts/gdal_calc.py"&gt;gdal_calc.py&lt;/a&gt; utility that comes with recent versions of gdal. This allows you to apply numpy's trigonometric functions to a raster...&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;gdal_calc.py -A aspect.tif --calc &amp;quot;cos(radians(A))&amp;quot; --format &amp;quot;GTiff&amp;quot; --outfile cos_aspect.tif  
gdal_calc.py -A aspect.tif --calc &amp;quot;sin(radians(A))&amp;quot; --format &amp;quot;GTiff&amp;quot; --outfile sin_aspect.tif
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Now we can look at the sum of the cos/sin grid cells for our area and take the arctangent according to this python code&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;math&lt;/span&gt;
&lt;span class="n"&gt;avg_aspect_rad&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;math&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;atan2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cos_cells&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sin_cells&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;avg_aspect_deg&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;math&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;degrees&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;avg_aspect_rad&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;avg_aspect_deg&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;In our example avg_aspect_deg comes out to an aspect of 0 degrees (due north) which is exactly what we'd expect. &lt;/p&gt;
&lt;p&gt;Thanks to Dan Patterson for his &lt;a href="http://forums.esri.com/Thread.asp?c=3&amp;amp;f=40&amp;amp;t=119358&amp;amp;mc=8#343468"&gt;forum post&lt;/a&gt; which clued me into this approach. &lt;/p&gt;</content><category term="articles"></category><category term="dem"></category><category term="trig"></category><category term="gdal"></category></entry><entry><title>UTFGrids with OpenLayers and Tilestache</title><link href="https://www.perrygeo.com/utfgrids-with-openlayers-and-tilestache.html" rel="alternate"></link><published>2012-02-24T00:00:00-07:00</published><updated>2012-02-24T00:00:00-07:00</updated><author><name>Matthew T. Perry</name></author><id>tag:www.perrygeo.com,2012-02-24:/utfgrids-with-openlayers-and-tilestache.html</id><summary type="html">&lt;p&gt;A while back, the Development Seed team developed the &lt;a href="http://mapbox.com/mbtiles-spec/utfgrid/"&gt;UTFGrid spec&lt;/a&gt; to provide&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;a standard, scalable way of encoding data for hundreds or thousands of features alongside your map tiles.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;The basics&lt;/h3&gt;
&lt;p&gt;In more detail, the UTFGrids are invisible "ASCII Art" and attribute data embedded in json. They sit "behind …&lt;/p&gt;</summary><content type="html">&lt;p&gt;A while back, the Development Seed team developed the &lt;a href="http://mapbox.com/mbtiles-spec/utfgrid/"&gt;UTFGrid spec&lt;/a&gt; to provide&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;a standard, scalable way of encoding data for hundreds or thousands of features alongside your map tiles.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;The basics&lt;/h3&gt;
&lt;p&gt;In more detail, the UTFGrids are invisible "ASCII Art" and attribute data embedded in json. They sit "behind" your map tiles (they are not rendered visually) and allows quick attribute lookups &lt;em&gt;without&lt;/em&gt; going back to the server. This allows a high degree of real-time map interactivity in an HTML web map - something that has typically been the strong point of plugin-based maps. &lt;/p&gt;
&lt;p&gt;So take this tile image...&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="http://vmap0.tiles.osgeo.org/wms/vmap0?LAYERS=basic&amp;amp;SERVICE=WMS&amp;amp;VERSION=1.1.1&amp;amp;REQUEST=GetMap&amp;amp;STYLES=&amp;amp;FORMAT=image%2Fjpeg&amp;amp;SRS=EPSG%3A900913&amp;amp;BBOX=-0.0007999986410141,5009377.084,5009377.084,10018754.1688&amp;amp;WIDTH=256&amp;amp;HEIGHT=256"&gt; &lt;/p&gt;
&lt;p&gt;and it's corresponding "utfgrid" ...&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="err"&gt;######&lt;/span&gt;&lt;span class="o"&gt;$$$$%%%&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;%%%%&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="err"&gt;#######&lt;/span&gt;&lt;span class="o"&gt;$$$$%%%&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;%%%&lt;/span&gt;
&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="o"&gt;!!&lt;/span&gt;&lt;span class="err"&gt;#####&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="o"&gt;$$$%%%&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;%%%&lt;/span&gt;
&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="err"&gt;######&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;$$$$%%%&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;%%&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;%%%&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="o"&gt;!!!&lt;/span&gt;&lt;span class="err"&gt;####&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;$$$$$%%%%&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;%%%%&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="err"&gt;######&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;$$$$$$%%%%%%%%%%&lt;/span&gt;
&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!!&lt;/span&gt;&lt;span class="err"&gt;#####&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;$$$$$$$%%%%%%%%%&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;!!!!!&lt;/span&gt;&lt;span class="err"&gt;####&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="o"&gt;$$$$$$%%%%%%%%%%&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;!!!!!&lt;/span&gt;&lt;span class="err"&gt;####&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="o"&gt;$$$$$$%%%%%%%%%%&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;!!!!!&lt;/span&gt;&lt;span class="err"&gt;####&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="o"&gt;$$$$$%%%%%%%%%%%&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;!!!!!&lt;/span&gt;&lt;span class="err"&gt;#####&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;$$&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="o"&gt;%%%%%%%%%%%&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;!!!!!&lt;/span&gt;&lt;span class="err"&gt;###&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="o"&gt;%%%%%%%%%%%%&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;!!!&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;#####&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;%%%%%%%%%%%%&lt;/span&gt;
&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="err"&gt;###&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;%%%%%%%%%%%%&lt;/span&gt;
&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;###&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;((%%%%%%%%%%%%&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;##&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="o"&gt;(((((%%%%%%%%%%%%&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;****(+%%%%%%%%%%%&lt;/span&gt;
&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="o"&gt;%**++++%%%%%%%%%&lt;/span&gt;
&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;------&lt;/span&gt;&lt;span class="o"&gt;*+++++%%%%%%%%%&lt;/span&gt;
&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="o"&gt;,,,,,&lt;/span&gt;&lt;span class="nt"&gt;------&lt;/span&gt;&lt;span class="o"&gt;+++++++%%%%%%%%&lt;/span&gt;
&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;/,,,,,,&lt;/span&gt;&lt;span class="nt"&gt;------&lt;/span&gt;&lt;span class="o"&gt;++++++%%%%%%%%%&lt;/span&gt;
&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;//,,,,,,&lt;/span&gt;&lt;span class="nt"&gt;------000&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="nt"&gt;000&lt;/span&gt;&lt;span class="o"&gt;%%%%%%%&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;211&lt;/span&gt;&lt;span class="o"&gt;,,,,,&lt;/span&gt;&lt;span class="nt"&gt;33------00000000&lt;/span&gt;&lt;span class="o"&gt;%%%%%%&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;2221&lt;/span&gt;&lt;span class="o"&gt;,,,,&lt;/span&gt;&lt;span class="nt"&gt;33333---00000000000&lt;/span&gt;&lt;span class="o"&gt;%%%%&lt;/span&gt;
&lt;span class="nt"&gt;222222&lt;/span&gt;&lt;span class="o"&gt;,,,,&lt;/span&gt;&lt;span class="nt"&gt;3635550000000000000&lt;/span&gt;&lt;span class="o"&gt;%%%&lt;/span&gt;
&lt;span class="nt"&gt;222222&lt;/span&gt;&lt;span class="o"&gt;,,,,&lt;/span&gt;&lt;span class="nt"&gt;6665777008900000000&lt;/span&gt;&lt;span class="o"&gt;%%%&lt;/span&gt;
&lt;span class="nt"&gt;22222&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nd"&gt;66666777788889900000&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;%%%%&lt;/span&gt;
&lt;span class="nt"&gt;22222&lt;/span&gt;&lt;span class="o"&gt;:;;;;%%=&lt;/span&gt;&lt;span class="nt"&gt;7&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="nt"&gt;8888890&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;0&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="o"&gt;%%%%&lt;/span&gt;
&lt;span class="nt"&gt;22222&lt;/span&gt;&lt;span class="o"&gt;;;;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==??%%&lt;/span&gt;&lt;span class="nt"&gt;888888&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;00&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;%%%%%&lt;/span&gt;
&lt;span class="nt"&gt;222222&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;;;&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;=??%%%&lt;/span&gt;&lt;span class="nt"&gt;8888&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="o"&gt;%%%%&lt;/span&gt;
&lt;span class="nt"&gt;222&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="o"&gt;;;&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="nt"&gt;A&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;@@@&lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="nt"&gt;B&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;
&lt;span class="nt"&gt;CCC&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="o"&gt;;;&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="nt"&gt;DEE&lt;/span&gt;&lt;span class="o"&gt;@@@&lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="nt"&gt;BB&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;You can see how each character corresponds with a country. The character's code is used as a lookup key to retrieve the data associated with that feature (which is also included in the json tile).&lt;/p&gt;
&lt;p&gt;If you want to dig in, check out the &lt;a href="http://mapbox.com/demo/visiblemap/"&gt;mapbox demo&lt;/a&gt;. &lt;/p&gt;
&lt;h3&gt;The Server side&lt;/h3&gt;
&lt;p&gt;I'm going to assume you have &lt;a href="http://tilestache.org/"&gt;Tilestache&lt;/a&gt; and &lt;a href="https://github.com/mapnik/mapnik"&gt;Mapnik 2+&lt;/a&gt; already installed (if not, you should!). The steps to configuring your server for UTFGrids are fairly simple.. &lt;/p&gt;
&lt;p&gt;&lt;strong&gt;First&lt;/strong&gt;, set up mapnik xml file pointing to your data source.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?xml version=&amp;quot;1.0&amp;quot;?&amp;gt;&lt;/span&gt;

&lt;span class="cm"&gt;&amp;lt;!-- An ultra simple Mapnik stylesheet --&amp;gt;&lt;/span&gt;

&lt;span class="cp"&gt;&amp;lt;!DOCTYPE Map [&lt;/span&gt;
&lt;span class="cp"&gt;&amp;lt;!ENTITY google_mercator &amp;quot;+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs +over&amp;quot;&amp;gt;&lt;/span&gt;
]&amp;gt;

&lt;span class="nt"&gt;&amp;lt;Map&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;srs=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&amp;amp;google_mercator;&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;Style&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;style&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;Rule&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;PolygonSymbolizer&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;CssParameter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;gamma&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;.65&lt;span class="nt"&gt;&amp;lt;/CssParameter&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;CssParameter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;fill&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;green&lt;span class="nt"&gt;&amp;lt;/CssParameter&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;CssParameter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;fill-opacity&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;0.5&lt;span class="nt"&gt;&amp;lt;/CssParameter&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/PolygonSymbolizer&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;LineSymbolizer&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;CssParameter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;stroke&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;#666&lt;span class="nt"&gt;&amp;lt;/CssParameter&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;CssParameter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;stroke-width&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;0.3&lt;span class="nt"&gt;&amp;lt;/CssParameter&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/LineSymbolizer&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/Rule&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/Style&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;Layer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;layer&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;srs=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&amp;amp;google_mercator;&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;StyleName&amp;gt;&lt;/span&gt;style&lt;span class="nt"&gt;&amp;lt;/StyleName&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;Datasource&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;Parameter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;type&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;shape&lt;span class="nt"&gt;&amp;lt;/Parameter&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;Parameter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;file&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;sample_data/world_merc.shp&lt;span class="nt"&gt;&amp;lt;/Parameter&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/Datasource&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/Layer&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/Map&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Next&lt;/strong&gt;, set up tilestache configuration file&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;{&lt;/span&gt;
&lt;span class="n"&gt;&amp;quot;cache&amp;quot;: {&lt;/span&gt;
&lt;span class="n"&gt;           &amp;quot;name&amp;quot;: &amp;quot;Disk&amp;quot;,&lt;/span&gt;
&lt;span class="n"&gt;           &amp;quot;path&amp;quot;: &amp;quot;/tmp/stache&amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;},&lt;/span&gt;
&lt;span class="n"&gt;&amp;quot;layers&amp;quot;: {&lt;/span&gt;
&lt;span class="n"&gt;    &amp;quot;world&amp;quot;:&lt;/span&gt;
&lt;span class="n"&gt;    {&lt;/span&gt;
&lt;span class="n"&gt;        &amp;quot;provider&amp;quot;: {&amp;quot;name&amp;quot;: &amp;quot;mapnik&amp;quot;, &amp;quot;mapfile&amp;quot;: &amp;quot;style.xml&amp;quot;}&lt;/span&gt;
&lt;span class="n"&gt;    },&lt;/span&gt;
&lt;span class="n"&gt;    &amp;quot;world_utfgrid&amp;quot;:&lt;/span&gt;
&lt;span class="n"&gt;    {&lt;/span&gt;
&lt;span class="n"&gt;        &amp;quot;provider&amp;quot;:&lt;/span&gt;
&lt;span class="n"&gt;        {&lt;/span&gt;
&lt;span class="n"&gt;        &amp;quot;class&amp;quot;: &amp;quot;TileStache.Goodies.Providers.MapnikGrid:Provider&amp;quot;,&lt;/span&gt;
&lt;span class="n"&gt;        &amp;quot;kwargs&amp;quot;:&lt;/span&gt;
&lt;span class="n"&gt;        {&lt;/span&gt;
&lt;span class="n"&gt;            &amp;quot;mapfile&amp;quot;: &amp;quot;style.xml&amp;quot;, &lt;/span&gt;
&lt;span class="n"&gt;            &amp;quot;fields&amp;quot;:[&amp;quot;NAME&amp;quot;, &amp;quot;POP2005&amp;quot;],&lt;/span&gt;
&lt;span class="n"&gt;            &amp;quot;layer_index&amp;quot;: 0,&lt;/span&gt;
&lt;span class="n"&gt;            &amp;quot;scale&amp;quot;: 4&lt;/span&gt;
&lt;span class="n"&gt;        }&lt;/span&gt;
&lt;span class="n"&gt;    }&lt;/span&gt;
&lt;span class="n"&gt;  }&lt;/span&gt;
&lt;span class="n"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Finally, you're ready to run the tilestache server...&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;tilestache-server.py -c your.cfg -i localhost -p 7890
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Now you should be serving utfgrids to &lt;code&gt;http://localhost:7890/world_utfgrid/&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;The Client side&lt;/h3&gt;
&lt;p&gt;Now we need something to consume the UTFGrid tiles and interact with them in an HTML/JS environment. The original client implementation of UTFGrid support is provided by &lt;a href="http://mapbox.com/wax/"&gt;Wax&lt;/a&gt; which sits atop mapping clients like Modest Maps and Leaflet. Wax is very slick and easy to use but doesn't work so well for more complex arrangements or with OpenLayers-based maps. &lt;/p&gt;
&lt;p&gt;Rather than clog up Wax with the complex UTFGrid use cases that we envisioned, we decided to implement a UTFGrid client in native OpenLayers. Hence my project for the &lt;a href="http://wiki.osgeo.org/wiki/IslandWood_Code_Sprint_2012"&gt;OSGEO code sprint&lt;/a&gt; was born.&lt;/p&gt;
&lt;p&gt;&lt;img alt="olexample.PNG" src="/assets/img/uploads/2012/02/olexample.PNG"&gt;&lt;/p&gt;
&lt;p&gt;The result was a new OpenLayers Layer which loads up the json "tiles" behind the scenes...&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;grid_layer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;OpenLayers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Layer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UTFGrid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Invisible UTFGrid Layer&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;./utfgrid/world_utfgrid/${z}/${x}/${y}.json&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;map&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;addLayer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;grid_layer&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;and an OpenLayers Control that handles how the mouse events interact with the grid. In this example, as the mouse moves over the map, a custom callback if fired off which updates a div with some attribute information.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="w"&gt;       &lt;/span&gt;var&lt;span class="w"&gt; &lt;/span&gt;callback&lt;span class="w"&gt; &lt;/span&gt;=&lt;span class="w"&gt; &lt;/span&gt;function(attributes)&lt;span class="w"&gt; &lt;/span&gt;{
&lt;span class="w"&gt;            &lt;/span&gt;if&lt;span class="w"&gt; &lt;/span&gt;(attributes)&lt;span class="w"&gt; &lt;/span&gt;{
&lt;span class="w"&gt;                &lt;/span&gt;var&lt;span class="w"&gt; &lt;/span&gt;msg&lt;span class="w"&gt;  &lt;/span&gt;=&lt;span class="w"&gt; &lt;/span&gt;&amp;quot;&lt;span class="nt"&gt;&amp;lt;strong&amp;gt;&lt;/span&gt;In&lt;span class="w"&gt; &lt;/span&gt;2005,&lt;span class="w"&gt; &lt;/span&gt;&amp;quot;&lt;span class="w"&gt; &lt;/span&gt;+&lt;span class="w"&gt; &lt;/span&gt;attributes.NAME&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;msg&lt;span class="w"&gt; &lt;/span&gt;+=&lt;span class="w"&gt; &lt;/span&gt;&amp;quot;&lt;span class="w"&gt; &lt;/span&gt;had&lt;span class="w"&gt; &lt;/span&gt;a&lt;span class="w"&gt; &lt;/span&gt;population&lt;span class="w"&gt; &lt;/span&gt;of&lt;span class="w"&gt; &lt;/span&gt;&amp;quot;&lt;span class="w"&gt; &lt;/span&gt;+&lt;span class="w"&gt; &lt;/span&gt;attributes.POP2005&lt;span class="w"&gt; &lt;/span&gt;+&lt;span class="w"&gt; &lt;/span&gt;&amp;quot;&lt;span class="w"&gt; &lt;/span&gt;people.&lt;span class="nt"&gt;&amp;lt;/strong&amp;gt;&lt;/span&gt;&amp;quot;;
&lt;span class="w"&gt;                &lt;/span&gt;var&lt;span class="w"&gt; &lt;/span&gt;element&lt;span class="w"&gt; &lt;/span&gt;=&lt;span class="w"&gt; &lt;/span&gt;OpenLayers.Util.getElement(&amp;#39;attrsdiv&amp;#39;);
&lt;span class="w"&gt;                &lt;/span&gt;element.innerHTML&lt;span class="w"&gt; &lt;/span&gt;=&lt;span class="w"&gt; &lt;/span&gt;msg;
&lt;span class="w"&gt;                &lt;/span&gt;return&lt;span class="w"&gt; &lt;/span&gt;true;
&lt;span class="w"&gt;            &lt;/span&gt;}&lt;span class="w"&gt; &lt;/span&gt;else&lt;span class="w"&gt; &lt;/span&gt;{
&lt;span class="w"&gt;                &lt;/span&gt;this.element.innerHTML&lt;span class="w"&gt; &lt;/span&gt;=&lt;span class="w"&gt; &lt;/span&gt;&amp;#39;&amp;#39;;
&lt;span class="w"&gt;                &lt;/span&gt;return&lt;span class="w"&gt; &lt;/span&gt;false;&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;}
&lt;span class="w"&gt;        &lt;/span&gt;}

&lt;span class="w"&gt;        &lt;/span&gt;var&lt;span class="w"&gt; &lt;/span&gt;control&lt;span class="w"&gt; &lt;/span&gt;=&lt;span class="w"&gt; &lt;/span&gt;new&lt;span class="w"&gt; &lt;/span&gt;OpenLayers.Control.UTFGrid({
&lt;span class="w"&gt;            &lt;/span&gt;&amp;#39;handlerMode&amp;#39;:&lt;span class="w"&gt; &lt;/span&gt;&amp;#39;move&amp;#39;,
&lt;span class="w"&gt;            &lt;/span&gt;&amp;#39;callback&amp;#39;:&lt;span class="w"&gt; &lt;/span&gt;callback
&lt;span class="w"&gt;        &lt;/span&gt;});
&lt;span class="w"&gt;        &lt;/span&gt;map.addControl(control);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Overall the design goal was to decouple the loading/tiling of the UTFGrids from the interactivity/control. I think this works out nicely and, while a bit more cumbersome than the method used by Wax, it is more flexible and integrates well with existing OpenLayers apps. &lt;/p&gt;
&lt;p&gt;You can see them in action on the examples pages:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Demonstrating the use of &lt;a href="http://labs.ecotrust.org/utfgrid/events.html"&gt;different event handlers&lt;/a&gt; (click, hover, move)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Demonstrating &lt;a href="http://labs.ecotrust.org/utfgrid/multi.html"&gt;multiple interactivity layers&lt;/a&gt; (the interactivity layer need not visible in the map tiles!)&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And feel free to check out the code at &lt;a href="https://github.com/perrygeo/openlayers/tree/utfgrid"&gt;my github fork&lt;/a&gt; for the code. &lt;/p&gt;
&lt;p&gt;What do you think? Let me know...&lt;/p&gt;</content><category term="articles"></category><category term="utfgrid"></category><category term="openlayers"></category><category term="tilestache"></category></entry><entry><title>Optimizing KML for hierarchical polygon data</title><link href="https://www.perrygeo.com/optimizing-kml-for-hierarchical-polygon-data.html" rel="alternate"></link><published>2011-05-18T00:00:00-06:00</published><updated>2011-05-18T00:00:00-06:00</updated><author><name>Matthew T. Perry</name></author><id>tag:www.perrygeo.com,2011-05-18:/optimizing-kml-for-hierarchical-polygon-data.html</id><summary type="html">&lt;p&gt;For all the benefits of KML, it is decidedly a step backwards for handling large vector datasets. Most KML clients, including the cannonical Google Earth application, experience debilitating slow-down when viewing a couple dozen MB of vector data - datasets that I could easily open on a Pentium 4 in ArcView …&lt;/p&gt;</summary><content type="html">&lt;p&gt;For all the benefits of KML, it is decidedly a step backwards for handling large vector datasets. Most KML clients, including the cannonical Google Earth application, experience debilitating slow-down when viewing a couple dozen MB of vector data - datasets that I could easily open on a Pentium 4 in ArcView 3.2 10 years ago! &lt;/p&gt;
&lt;p&gt;The unfortunate reality is that optimizing the performance of KML datasets is conflated with the structure of the data and is thus the responsibility of the data publisher. The wisdom of combining styling, performance-related structure, organizational structure, geometry and attributes into a single file format may be questionable, but KML has become the defacto geographic markup language due to it's other benefits. &lt;/p&gt;
&lt;p&gt;Anyways, back to performance enhancements on big vector datasets... The concept of "regionation" is used by several KML software to improve performance. From the &lt;a href="http://google-latlong.blogspot.com/2010/09/faster-larger-closer-regionation-in.html"&gt;Google LatLong Blog&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;You can think of Regionation as a &lt;strong&gt;hierarchical subdivision of points or tiles&lt;/strong&gt;, which shows less detail from afar, and more detail as you zoom in to the globe. This dynamic loading creates clearer visualizations by minimizing clutter, while simultaneously speeding up the rendering process.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;In most implementations, there is a generic strategy for determining this hierarchy based on attributes or geometry size (in the case of vectors) or by a tile system. Neither is ideal when you want to preserve the vector nature of the data, split it into small, easily-loadable files and determine it's view based on the &lt;strong&gt;natural hierarchy that is built into the data structure&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Specifically I am thinking about watersheds here - the US &lt;a href="http://nwis.waterdata.usgs.gov/tutorial/huc_def.html"&gt;Hydrologic Units&lt;/a&gt;. Hydrologic units are watershed boundaries that are organized in a nested hierarchy; higher levels contain smaller watersheds that are contained within a single watershed from a "parent" level. The unique identifiers (hydrologic unit codes or HUCs) are rather ingenious as well; Each level is represented by 2 digits and are concatenated to form a single identifier that can be used to determine it's "parent". For example:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Level 4 HUCs" src="/assets/img/uploads/2011/05/huc8.png"&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="Level 5 HUCs" src="/assets/img/uploads/2011/05/huc10.png"&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="Level 6 HUCs" src="/assets/img/uploads/2011/05/huc12.png"&gt;&lt;/p&gt;
&lt;p&gt;Level 4 HUCs &lt;br&gt;
e.g. 170900&lt;strong&gt;11&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Level 5 HUCs &lt;br&gt;
e.g. 17090011&lt;strong&gt;04&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Level 6 HUCs &lt;br&gt;
e.g. 1709001104&lt;strong&gt;03&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Instead of fabricating a hierarchy of features, why not just use this natural hierarchy to structure the KML documents?&lt;/p&gt;
&lt;p&gt;&lt;img alt="hucs-1.png" src="/assets/img/uploads/2011/05/hucs-1.png"&gt;&lt;/p&gt;
&lt;p&gt;Or as KML markup:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;placemark&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;name&amp;gt;&lt;/span&gt;17090009&lt;span class="nt"&gt;&amp;lt;/name&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;styleurl&amp;gt;&lt;/span&gt;#HUC_8-default&lt;span class="nt"&gt;&amp;lt;/styleurl&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;polygon&amp;gt;&amp;lt;outerboundaryis&amp;gt;&amp;lt;linearring&amp;gt;&amp;lt;coordinates&amp;gt;&lt;/span&gt;...
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/coordinates&amp;gt;&amp;lt;/linearring&amp;gt;&amp;lt;/outerboundaryis&amp;gt;&amp;lt;/polygon&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/placemark&amp;gt;&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;networklink&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;name&amp;gt;&lt;/span&gt;17090009_children&lt;span class="nt"&gt;&amp;lt;/name&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;region&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;latlonaltbox&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;west&amp;gt;&lt;/span&gt;-123.001645628&lt;span class="nt"&gt;&amp;lt;/west&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;south&amp;gt;&lt;/span&gt;44.8300083641&lt;span class="nt"&gt;&amp;lt;/south&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;east&amp;gt;&lt;/span&gt;-122.203351254&lt;span class="nt"&gt;&amp;lt;/east&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;north&amp;gt;&lt;/span&gt;45.298653051&lt;span class="nt"&gt;&amp;lt;/north&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/latlonaltbox&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;lod&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;minlodpixels&amp;gt;&lt;/span&gt;256&lt;span class="nt"&gt;&amp;lt;/minlodpixels&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;maxlodpixels&amp;gt;&lt;/span&gt;1600&lt;span class="nt"&gt;&amp;lt;/maxlodpixels&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/lod&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/region&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;link&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;href&amp;gt;&lt;/span&gt;./17090009_children.kml&lt;span class="nt"&gt;&amp;lt;/href&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;viewrefreshmode&amp;gt;&lt;/span&gt;onRegion&lt;span class="nt"&gt;&amp;lt;/viewrefreshmode&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/link&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/networklink&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The advantages to this design are that you don't have to break the geometries up to fit into a square tiling pattern, data loads and renders in a logical pattern and there will always be 100 or less (usually far less) placemarks per file due to the design of the HUC data structure. File sizes stay low, network links load quickly and request/rendering occurs only when they come into view. For this example dataset totaling 300M of shapefiles, there are several hundred resulting kmz files without any repeated features and all less than ~ 150K each. In essence, it achieves optimal performance by its very design. &lt;/p&gt;
&lt;p&gt;Here's a video of it in action:&lt;/p&gt;
&lt;iframe width="420" height="315" src="http://www.youtube.com/embed/5FgOfLEVX8M" frameborder="0"&gt;iframecontent&lt;/iframe&gt;

&lt;p&gt;This was all done with &lt;a href="http://watershed-priorities.googlecode.com/hg/util/kml_regionate_heirarchy.py"&gt;a fairly "hackish" python script&lt;/a&gt;. I'll continue to refine it as needed for this particular application but, at this time, it's not intended to be a reusable tool - if you want to use it, be prepared to dig through the source code and get your hands dirty. The same concept could theoretically be applied to any spatially-hierarchical vector data (think geographic boundaries ... country &amp;gt; state &amp;gt; county &amp;gt; city).&lt;/p&gt;</content><category term="articles"></category><category term="hydrology"></category><category term="kml"></category><category term="python"></category></entry><entry><title>Um - nice “review” of QGIS</title><link href="https://www.perrygeo.com/um-nice-review-of-qgis.html" rel="alternate"></link><published>2010-12-20T00:00:00-07:00</published><updated>2010-12-20T00:00:00-07:00</updated><author><name>Matthew T. Perry</name></author><id>tag:www.perrygeo.com,2010-12-20:/um-nice-review-of-qgis.html</id><summary type="html">&lt;p&gt;RJ Zimmer at American Surveyor magazine did what he described as a comparison of several free GIS application entitled "&lt;a href="http://www.amerisurv.com/PDF/TheAmericanSurveyor_Zimmer-SomethingForNothing_Vol7No8.pdf"&gt;Something for Nothing&lt;/a&gt;"&lt;/p&gt;
&lt;p&gt;First of all, the title bugs me. The idea that the sole benefit of free software is simply cost savings is pretty naive. It disregards openness, community support …&lt;/p&gt;</summary><content type="html">&lt;p&gt;RJ Zimmer at American Surveyor magazine did what he described as a comparison of several free GIS application entitled "&lt;a href="http://www.amerisurv.com/PDF/TheAmericanSurveyor_Zimmer-SomethingForNothing_Vol7No8.pdf"&gt;Something for Nothing&lt;/a&gt;"&lt;/p&gt;
&lt;p&gt;First of all, the title bugs me. The idea that the sole benefit of free software is simply cost savings is pretty naive. It disregards openness, community support, ability to transfer knowledge, freedom from restrictive licensing, etc. But I can live with the title.&lt;/p&gt;
&lt;p&gt;I can also live with his decision to include only a single open-source GIS application alongside 3 closed-but-gratis applications. He doesn't claim that it's a comprehensive review despite the fact that the ecosystem of Free GIS is far more diverse.&lt;/p&gt;
&lt;p&gt;But I can't accept his treatment of Quantum GIS:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I did not fully test Quantum GIS. I did download and install it but the software was too complicated to use "right out of the box", and I did not have time to learn to use it.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The feature comparison chart includes mainly "?" in the QGIS column. &lt;/p&gt;
&lt;p&gt;OK we get it - your deadline hit before you could bother to learn one of the applications you were supposedly reviewing. One even wonders why he included QGIS the review at all. This is nothing short of irresponsible reporting. When people post stuff like this, it really rubs me the wrong way - now a whole audience of users have a inaccurate view of QGIS and entire free GIS ecosystem thanks to his slacker journalism.&lt;/p&gt;</content><category term="articles"></category><category term="qgis"></category></entry><entry><title>kmltree</title><link href="https://www.perrygeo.com/kmltree.html" rel="alternate"></link><published>2010-06-09T00:00:00-06:00</published><updated>2010-06-09T00:00:00-06:00</updated><author><name>Matthew T. Perry</name></author><id>tag:www.perrygeo.com,2010-06-09:/kmltree.html</id><summary type="html">&lt;p&gt;When the &lt;a href="http://marinemap.org"&gt;MarineMap&lt;/a&gt; team started delving into the &lt;a href="http://earth.google.com/plugin/"&gt;Google Earth plugin&lt;/a&gt;, it was apparent that it supported the display and rendering of KML files &lt;em&gt;almost&lt;/em&gt; as well as the Google Earth desktop application. The missing piece of functionality was the nice tree-style legend that is provided with the desktop app …&lt;/p&gt;</summary><content type="html">&lt;p&gt;When the &lt;a href="http://marinemap.org"&gt;MarineMap&lt;/a&gt; team started delving into the &lt;a href="http://earth.google.com/plugin/"&gt;Google Earth plugin&lt;/a&gt;, it was apparent that it supported the display and rendering of KML files &lt;em&gt;almost&lt;/em&gt; as well as the Google Earth desktop application. The missing piece of functionality was the nice tree-style legend that is provided with the desktop app. The plugin lets you add KML for display but gives you no HTML interface to work with it. For simple apps, you can just roll your own html/js form. But that quickly becomes unmanageable if you're adding KML dynamically and need to create a tree-style legend for any arbitrary KML document. &lt;/p&gt;
&lt;p&gt;Enter &lt;a href="http://code.google.com/p/kmltree/"&gt;kmltree&lt;/a&gt;. &lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;kmltree is a javascript tree widget that can be used in conjunction with the Google Earth API. It replicates the functionality of the Google Earth desktop client, and is fast, extensible, and stable for use in advanced web applications. It's built utilizing the earth-api-utility-library and jQuery. &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;a href="/assets/img/uploads/2010/06/screen-shot-2010-06-09-at-81707-am.png"&gt;&lt;img alt="kmltree" src="/assets/img/uploads/2010/06/screen-shot-2010-06-09-at-81707-am.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Any arbitrary KML can be parsed and represented in a tree-style legend right in the web browser. &lt;a href="http://kmltree.googlecode.com/hg/examples/refresh.html"&gt;Try it out&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Kmltree is the brainchild of &lt;a href="http://www.google.com/profiles/underbluewaters"&gt;Chad Burt&lt;/a&gt; who developed it as part of the marinemap codebase but had the foresight to realize that this would be useful to a much wider audience and abstracted it into its own javascript library. If you're building a web mapping application with the Google Earth API, give it a shot!&lt;/p&gt;</content><category term="articles"></category><category term="marinemap"></category><category term="kmltree"></category><category term="kml"></category></entry><entry><title>MarineMap wins award for Environmental Conflict Resolution</title><link href="https://www.perrygeo.com/marinemap-wins-award-for-environmental-conflict-resolution.html" rel="alternate"></link><published>2010-05-27T00:00:00-06:00</published><updated>2010-05-27T00:00:00-06:00</updated><author><name>Matthew T. Perry</name></author><id>tag:www.perrygeo.com,2010-05-27:/marinemap-wins-award-for-environmental-conflict-resolution.html</id><summary type="html">&lt;p&gt;For the last year or so, I've had the pleasure of working with the &lt;a href="http://www.marinemap.org"&gt;MarineMap Consortium&lt;/a&gt;. We just learned yesterday that the U.S. Institute for Environmental Conflict Resolution &lt;a href="http://eon.businesswire.com/portal/site/eon/permalink/?ndmViewId=news_view&amp;amp;newsId=20100526007072&amp;amp;newsLang=en"&gt;awarded&lt;/a&gt; MarineMap the “Innovation in Technology and Environmental Conflict Resolution”.&lt;/p&gt;
&lt;iframe width="560" height="315" src="http://www.youtube.com/embed/GCUxpnUSiUg" frameborder="0"&gt; x&lt;/iframe&gt;

&lt;p&gt;I joined the team after the launch of the &lt;a href="http://southcoast.marinemap.org/marinemap/"&gt;South …&lt;/a&gt;&lt;/p&gt;</summary><content type="html">&lt;p&gt;For the last year or so, I've had the pleasure of working with the &lt;a href="http://www.marinemap.org"&gt;MarineMap Consortium&lt;/a&gt;. We just learned yesterday that the U.S. Institute for Environmental Conflict Resolution &lt;a href="http://eon.businesswire.com/portal/site/eon/permalink/?ndmViewId=news_view&amp;amp;newsId=20100526007072&amp;amp;newsLang=en"&gt;awarded&lt;/a&gt; MarineMap the “Innovation in Technology and Environmental Conflict Resolution”.&lt;/p&gt;
&lt;iframe width="560" height="315" src="http://www.youtube.com/embed/GCUxpnUSiUg" frameborder="0"&gt; x&lt;/iframe&gt;

&lt;p&gt;I joined the team after the launch of the &lt;a href="http://southcoast.marinemap.org/marinemap/"&gt;South Coast of California&lt;/a&gt; site which was already widely recognized as a successful decision-support tool for marine spatial planning. We've since been working on version 2 of the MarineMap tool which is deployed currently for the &lt;a href="http://northcoast.marinemap.org/marinemap"&gt;North Coast of California&lt;/a&gt; in support of their Marine Life Protection Act (MLPA) process. &lt;/p&gt;
&lt;p&gt;It's been a tremendous challenge to bring a &lt;a href="http://code.google.com/p/marinemap/"&gt;new version of the software&lt;/a&gt; to life and have it meet and exceed the standards set by its predecessor. It has also been tremendously rewarding and having our work recognized at this level is a great honor. It's nice to know that the tools we've developed have been so helpful and instrumental in the marine planning process along the coast of California. Looking forward, I see MarineMap growing beyond a tool for a specific purpose (supporting the MLPA Initiative) to a robust framework for developing web-based spatial planning tools for all sorts of environmental applications, both marine and terrestrial. And this award confirms that we are already heading in the right direction. Very exciting news!&lt;/p&gt;</content><category term="articles"></category><category term="marinemap"></category><category term="edss"></category></entry><entry><title>Exploring Geometry</title><link href="https://www.perrygeo.com/exploring-geometry.html" rel="alternate"></link><published>2010-05-06T00:00:00-06:00</published><updated>2010-05-06T00:00:00-06:00</updated><author><name>Matthew T. Perry</name></author><id>tag:www.perrygeo.com,2010-05-06:/exploring-geometry.html</id><summary type="html">&lt;p&gt;I don't know how I let this gem slip past my radar for so long. It was only via &lt;a href="http://lin-ear-th-inking.blogspot.com/2010/05/random-points-in-polygon-in-jts.html"&gt;a post by Dr. JTS&lt;/a&gt; himself (aka Martin Davis) that I saw a screenshot of JTS TestBuilder and decided to check it out. &lt;/p&gt;
&lt;p&gt;I was actually just talking with someone about …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I don't know how I let this gem slip past my radar for so long. It was only via &lt;a href="http://lin-ear-th-inking.blogspot.com/2010/05/random-points-in-polygon-in-jts.html"&gt;a post by Dr. JTS&lt;/a&gt; himself (aka Martin Davis) that I saw a screenshot of JTS TestBuilder and decided to check it out. &lt;/p&gt;
&lt;p&gt;I was actually just talking with someone about a tool that could provide simple visualization of WKT geometries;  JTS Test Builder does that and much more. &lt;/p&gt;
&lt;p&gt;You can input geometries (graphically or by well-known text) and compare two geometries based on spatial predicates:&lt;/p&gt;
&lt;p&gt;&lt;a href="/assets/img/uploads/2010/05/screen-shot-2010-05-06-at-81418-pm.png"&gt;&lt;img alt="spatial predicates" src="/assets/img/uploads/2010/05/screen-shot-2010-05-06-at-81418-pm.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Do overlay analyses with the two geometries. Note that you can see the result as WKT below.&lt;/p&gt;
&lt;p&gt;&lt;a href="/assets/img/uploads/2010/05/screen-shot-2010-05-06-at-81502-pm.png"&gt;&lt;img alt="overlay" src="/assets/img/uploads/2010/05/screen-shot-2010-05-06-at-81502-pm.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;And there are a host of other spatial operations to generate geometries using buffers...
&lt;a href="/assets/img/uploads/2010/05/screen-shot-2010-05-06-at-81602-pm.png"&gt;&lt;img alt="buffers" src="/assets/img/uploads/2010/05/screen-shot-2010-05-06-at-81602-pm.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;... convex hulls ...
&lt;a href="/assets/img/uploads/2010/05/screen-shot-2010-05-06-at-81716-pm.png"&gt;&lt;img alt="convex hull" src="/assets/img/uploads/2010/05/screen-shot-2010-05-06-at-81716-pm.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This app provides a very nice and user-friendly way to quickly and simply explore and test geometric operations. To try it out, &lt;a href="http://sourceforge.net/projects/jts-topo-suite/"&gt;download JTS&lt;/a&gt; and unzip the contents somewhere. If you're on windows, the .bat file is provided. If you're running anything else, you have to cook up a shell script that will set up the environment and run JTS TestBuilder:&lt;/p&gt;
&lt;blockquote&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nt"&gt;JTS_HOME&lt;/span&gt;&lt;span class="o"&gt;=/&lt;/span&gt;&lt;span class="nt"&gt;usr&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;share&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;java&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;jts-1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;11&lt;/span&gt;
&lt;span class="nt"&gt;CP&lt;/span&gt;&lt;span class="o"&gt;=$&lt;/span&gt;&lt;span class="nt"&gt;CLASSPATH&lt;/span&gt;
&lt;span class="nt"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="nt"&gt;JTS_HOME&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;/*&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;jar&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;do&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;CP&lt;/span&gt;&lt;span class="o"&gt;=$&lt;/span&gt;&lt;span class="nt"&gt;i&lt;/span&gt;&lt;span class="o"&gt;:$&lt;/span&gt;&lt;span class="nt"&gt;CP&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;done&lt;/span&gt;
&lt;span class="nt"&gt;java&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Xmx256m&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-cp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="nt"&gt;CP&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;com&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;vividsolutions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;jtstest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;testbuilder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;JTSTestBuilder&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;$*&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;/blockquote&gt;</content><category term="articles"></category><category term="jts"></category></entry><entry><title>Distributed</title><link href="https://www.perrygeo.com/distributed.html" rel="alternate"></link><published>2010-03-31T00:00:00-06:00</published><updated>2010-03-31T00:00:00-06:00</updated><author><name>Matthew T. Perry</name></author><id>tag:www.perrygeo.com,2010-03-31:/distributed.html</id><summary type="html">&lt;p&gt;I've been playing around with some distributed version control systems (DVCS) to replace svn. &lt;/p&gt;
&lt;p&gt;First, the &lt;em&gt;why&lt;/em&gt;: I'll leave the details up to Joel in his excellent &lt;a href="http://hginit.com/"&gt;HgInit tutorial&lt;/a&gt;. Its mercurial-specific but the general concepts apply to any DVCS. The takeaway message for any project with &amp;gt; 1 developer is this …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I've been playing around with some distributed version control systems (DVCS) to replace svn. &lt;/p&gt;
&lt;p&gt;First, the &lt;em&gt;why&lt;/em&gt;: I'll leave the details up to Joel in his excellent &lt;a href="http://hginit.com/"&gt;HgInit tutorial&lt;/a&gt;. Its mercurial-specific but the general concepts apply to any DVCS. The takeaway message for any project with &amp;gt; 1 developer is this:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Mercurial [ed: DVCS] separates the act of committing new code from the act of inflicting it on everybody else.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Next, the &lt;em&gt;implementation&lt;/em&gt;: I'm using &lt;strong&gt;git&lt;/strong&gt; to work on another project (&lt;a href="http://goldencheetah.org/"&gt;Golden Cheetah&lt;/a&gt;) and its been a tough learning curve. Git is no doubt the most powerful DVCS out there. You can do magical things with it like combine commits and mess with history trees. And you can also screw things up pretty badly if you misinterpret the esotric docs for some non-intuitive piece of the workflow. &lt;/p&gt;
&lt;p&gt;I just tried &lt;strong&gt;mercurial&lt;/strong&gt; this morning - hg seems to fit my mind well. There is less power but the workflow is very clear and intuitive. And there are docs written for people who don't want to do an in-depth study of their version control software. It stays out of the way. &lt;/p&gt;
&lt;p&gt;Long story short, I'm going to use mercurial/hg for my new projects. Ah what the heck my old/ongoing projects as well. My &lt;a href="http://code.google.com/p/perrygeo/"&gt;googlecode repository&lt;/a&gt; has been converted over to Mercurial. Svn will stick around but wont be updated.&lt;/p&gt;</content><category term="articles"></category></entry><entry><title>Lazy raster processing with GDAL VRTs</title><link href="https://www.perrygeo.com/lazy-raster-processing-with-gdal-vrts.html" rel="alternate"></link><published>2010-02-18T00:00:00-07:00</published><updated>2010-02-18T00:00:00-07:00</updated><author><name>Matthew T. Perry</name></author><id>tag:www.perrygeo.com,2010-02-18:/lazy-raster-processing-with-gdal-vrts.html</id><summary type="html">&lt;p&gt;No, not lazy as in REST :-) ... Lazy as in "&lt;a href="http://en.wikipedia.org/wiki/Lazy_evaluation"&gt;Lazy evaluation&lt;/a&gt;":&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;In computer programming, lazy evaluation is the technique of delaying a computation until the result is required.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Take an &lt;strong&gt;example raster processing workflow&lt;/strong&gt; to go from a bunch of tiled, latlong, GeoTiff digital elevation models to a single shaded …&lt;/p&gt;</summary><content type="html">&lt;p&gt;No, not lazy as in REST :-) ... Lazy as in "&lt;a href="http://en.wikipedia.org/wiki/Lazy_evaluation"&gt;Lazy evaluation&lt;/a&gt;":&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;In computer programming, lazy evaluation is the technique of delaying a computation until the result is required.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Take an &lt;strong&gt;example raster processing workflow&lt;/strong&gt; to go from a bunch of tiled, latlong, GeoTiff digital elevation models to a single shaded relief GeoTiff in projected space:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Merge the tiles together &lt;/li&gt;
&lt;li&gt;Reproject the merged DEM (using bilinear or cubic interpolation) &lt;/li&gt;
&lt;li&gt;Generate the hillshade from the merged DEM &lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Simple enough to do with GDAL tools on the command line. Here's the typical, &lt;strong&gt;process-as-you-go&lt;/strong&gt; implementation:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;gdal_merge.py -of GTiff -o srtm_merged.tif srtm_12_*.tif 
gdalwarp -t_srs epsg:3310 -r bilinear -of GTiff srtm_merged.tif srtm_merged_3310.tif 
gdaldem hillshade srtm_merged_3310.tif srtm_merged_3310_shade.tif -of GTiff
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Alternately, we can simulate &lt;strong&gt;lazy evaluation&lt;/strong&gt; by using &lt;a href="http://www.gdal.org/gdal_vrttut.html"&gt;GDAL Virtual Rasters&lt;/a&gt; (VRT) to perform the intermediate steps, only outputting the GeoTiff as the final step. &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;gdalbuildvrt srtm_merged.vrt srtm_12_0*.tif
gdalwarp -t_srs epsg:3310 -r bilinear -of VRT srtm_merged.vrt srtm_merged_3310.vrt 
gdaldem hillshade srtm_merged_3310.vrt srtm_merged_3310_shade2.tif -of GTiff
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;So what's the advantage to doing it the VRT way? They both produce &lt;em&gt;exactly&lt;/em&gt; the same output raster. Lets compare:&lt;/p&gt;
&lt;table class="table table-striped table-bordered table-condensed"&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt; &lt;/th&gt;
&lt;th&gt;Process-As-You-Go&lt;/th&gt;
&lt;th&gt;"Lazy" VRTs&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;th&gt;Merge (#1) time&lt;/th&gt;
&lt;td&gt;3.1 sec&lt;/td&gt;
&lt;td&gt;0.05 sec&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;Warp (#2) time &lt;/th&gt;
&lt;td&gt;7.3 sec &lt;/td&gt;
&lt;td&gt;0.10 sec &lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;Hillshade (#3) time&lt;/th&gt;
&lt;td&gt;10.5 sec &lt;/td&gt;
&lt;td&gt;19.75 sec&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;Total processing time&lt;/th&gt;
&lt;td&gt;20.9 sec&lt;/td&gt;
&lt;td&gt;19.9 sec &lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;Intermediate files&lt;/th&gt;
&lt;td&gt;2 tifs&lt;/td&gt;
&lt;td&gt;2 vrts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;Intermediate file size&lt;/th&gt;
&lt;td&gt;261 MB&lt;/td&gt;
&lt;td&gt;0.005 MB&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;The Lazy VRT method &lt;strong&gt;delays all the computationally-intensive processing until it is actually required&lt;/strong&gt;. The intermediate files, instead of containing the raw raster output of the actual computation, are XML files which contain the &lt;em&gt;instructions&lt;/em&gt; to get the desired output. This allows GDAL to do all the processing in one step (the final step #3). The &lt;em&gt;total&lt;/em&gt; processing time is not significantly different between the two methods but in terms of the productivity of the GIS analyst, the VRT method is superior. Imagine working with datasets 1000x this size with many more steps - having to type the command, wait 2 hours, type the next, etc. would be a waste of human resources versus assembling the instructions into vrts then hitting the final processing step when you leave the office for a long weekend.&lt;/p&gt;
&lt;p&gt;Additionaly, the VRT method produces only &lt;strong&gt;small intermediate xml files&lt;/strong&gt; instead of having a potentially huge data management nightmare of shuffling around GB (or TB) of intermediate outputs! Plus those xml files serve as an excellent piece of metadata which describe the exact processing steps which you can refer to later or adapt to different datasets. &lt;/p&gt;
&lt;p&gt;So next time you have a multi-step raster workflow, use the GDAL VRTs to your full advantage - you'll save yourself time and disk space by being lazy. &lt;/p&gt;</content><category term="articles"></category><category term="dem"></category><category term="gdal"></category></entry><entry><title>Peaksware licensing revisted …</title><link href="https://www.perrygeo.com/peaksware-licensing-revisted.html" rel="alternate"></link><published>2009-12-16T00:00:00-07:00</published><updated>2009-12-16T00:00:00-07:00</updated><author><name>Matthew T. Perry</name></author><id>tag:www.perrygeo.com,2009-12-16:/peaksware-licensing-revisted.html</id><summary type="html">&lt;p&gt;I had previously &lt;a href="http://www.perrygeo.net/wordpress/?p=138"&gt;bitched and moaned&lt;/a&gt; about the licensing restrictions on the &lt;a href="http://www.trainingpeaks.com/WKO"&gt;TrainingPeaks WKO+&lt;/a&gt; software. Truth be told, the reason I was so put off by their crappy licensing scheme was that my cycling training relied so heavily on their software. It was not perfect but it was the best …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I had previously &lt;a href="http://www.perrygeo.net/wordpress/?p=138"&gt;bitched and moaned&lt;/a&gt; about the licensing restrictions on the &lt;a href="http://www.trainingpeaks.com/WKO"&gt;TrainingPeaks WKO+&lt;/a&gt; software. Truth be told, the reason I was so put off by their crappy licensing scheme was that my cycling training relied so heavily on their software. It was not perfect but it was the best tool available. I've since discovered &lt;a href="http://goldencheetah.org/"&gt;Golden Cheetah&lt;/a&gt; which is a viable open-source alternative but it still lags behind WKO+ in many critical features.&lt;/p&gt;
&lt;p&gt;Now, fresh in time for the 2010 training season, Peaksware has released a new version 3.0 of WKO+ which, amongst many UI and functionality improvements, has made considerable progress on the licensing front.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;We know, our licensing has been a challenge to deal with for our customers in the past, but we’ve always tried to be as helpful as possible getting you back up and running after a hard drive crash or new computer. To remedy this, we’re pleased to announce an all new flexible licensing system. First, with every purchase we now allow you to install WKO+ 3.0 on up to two computers; second, we’ve built an online activation/deactivation system so you are free to move your active licenses from machine to machine. Are you leaving on a 2 week trip? Just de-activate your home computer, activate your laptop, and you’re on your way. When you get home, de-actiavate your laptop, re-activate your desktop and you’re all set.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;It ain't open source (there is still a place in this world for proprietary software if they can push the boundaries and innovate) but the sensitivity to the licensing issue just may have restored my faith in their company. &lt;/p&gt;</content><category term="articles"></category></entry><entry><title>Nice examples of ESRIs geoprocessing python module (9.3)</title><link href="https://www.perrygeo.com/nice-examples-of-esris-geoprocessing-python-module-93.html" rel="alternate"></link><published>2009-08-10T00:00:00-06:00</published><updated>2009-08-10T00:00:00-06:00</updated><author><name>Matthew T. Perry</name></author><id>tag:www.perrygeo.com,2009-08-10:/nice-examples-of-esris-geoprocessing-python-module-93.html</id><summary type="html">&lt;p&gt;Just thought I'd point out a great presentation about the "new" 9.3 geoprocessing (gp) python module from ESRI. &lt;/p&gt;
&lt;p&gt;Ghislain Prince and Elizabeth Flanary do a great job of introduction by examples. The latest gp module is much more pythonic and these examples show how to leverage that to its …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Just thought I'd point out a great presentation about the "new" 9.3 geoprocessing (gp) python module from ESRI. &lt;/p&gt;
&lt;p&gt;Ghislain Prince and Elizabeth Flanary do a great job of introduction by examples. The latest gp module is much more pythonic and these examples show how to leverage that to its full advantage. If you try to do this with older gp versions, the code would make most pythonistas cringe. This latest version returns objects and lists, use real booleans, and uses true objects instead of funky string parameters. Basic OO stuff for most python libraries but a big improvement for gp. &lt;/p&gt;
&lt;p&gt;Here's the &lt;a href="http://arcscripts.esri.com/details.asp?dbid=16509"&gt;powerpoint presentation&lt;/a&gt;. Thanks to Jamey Rosen for the tip!&lt;/p&gt;</content><category term="articles"></category></entry><entry><title>Peaksware licensing hell</title><link href="https://www.perrygeo.com/peaksware-licensing-hell.html" rel="alternate"></link><published>2009-06-23T00:00:00-06:00</published><updated>2009-06-23T00:00:00-06:00</updated><author><name>Matthew T. Perry</name></author><id>tag:www.perrygeo.com,2009-06-23:/peaksware-licensing-hell.html</id><summary type="html">&lt;p&gt;I've been using Peaksware's WKO+, a cycling and running training tool to manage data from heart rate monitors, GPS units, power meters, etc. Its a powerful tool with a clunky UI but I've gotten used to it. &lt;/p&gt;
&lt;p&gt;You pay $100 for a "personal" license. Not a big deal to me …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I've been using Peaksware's WKO+, a cycling and running training tool to manage data from heart rate monitors, GPS units, power meters, etc. Its a powerful tool with a clunky UI but I've gotten used to it. &lt;/p&gt;
&lt;p&gt;You pay $100 for a "personal" license. Not a big deal to me since they basically have a monopoly on this software niche. I first installed it on my work computer to test the data from my daily bike commute. Cool it works. Then I went to install it at home since that's where I'll be using it. Works ok. I proceed to gather all my fitness data into their proprietary binary format. &lt;/p&gt;
&lt;p&gt;Fast forward a few months. I'm reformatting the hard drive on the laptop and want to move all my data and software to my desktop. But installing WKO+ is giving me a headache ("Error: Too many installations"). The registration process takes a hardware fingerprint and your must active it via the web to get a registration code. However, hidden withing their EULA, is a term which &lt;strong&gt;dissallows the transfer of license&lt;/strong&gt; to another computer other than the one to which it was originally installed. The second installation was just an allowance they make to allow for "hard drive crashes" and such.&lt;/p&gt;
&lt;p&gt;Since neither of those machines would be available to me, certainly there would be a way to transfer it? After several progressively more desperate communications with Matt Allen at peaksware support, he informed me that there was no way they would transfer the license (the non-transfer clause IS in the EULA after all). &lt;strong&gt;I would need to purchase another license simply because I switched computers&lt;/strong&gt;!&lt;/p&gt;
&lt;p&gt;Here is my response:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Basically what you are telling me is that I can no longer use WKO+
without paying again. I get to use the software for a few months and
you revoke my right to use it because I buy a new computer! I am a
paying customer, trying to be totally legit here, willing to support
your business in exchange for a license to use your software and you
insist on screwing me over. Brilliant.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This is one of the most unprofessional and idiotic stances I have ever
seen from a software company. Your intention appears to be to screw
over your paying customers and milk as much cash from them as possible
- you might want to rethink that business model unless you want to
loose customers! I will never endorse, recommend or purchase another
product or service from peaksware nor will any of my family, friends,
teammates or readers once the word gets out about your disrespectful
policies.&lt;/p&gt;
&lt;p&gt;There are numerous typical situations where a new copy of the software
would need to be installed including:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Hard drive failure&lt;/li&gt;
&lt;li&gt;Operating system upgrades&lt;/li&gt;
&lt;li&gt;New computer purchases&lt;/li&gt;
&lt;li&gt;Extended traveling and touring (installing onto a laptop or netbook)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Now I fully understand why your policy is one license per computer. It
makes perfect sense. I have seen plenty of other software with a
similar licensing model. But they also allow to uninstall the software
and re-register it on another computer due to these circumstances.
There is simply no technological reason why you could not implement a
licensing structure that allowed the user more freedom to transfer
licenses while still preventing piracy. As it stands, your licensing
model treats paying customers like criminals if they happen to run
across any one of the above situations.&lt;/p&gt;
&lt;p&gt;So, to sum it up - your foolish license policy has lost you one
customer and many future ones.&lt;/p&gt;
&lt;p&gt;Good riddance.&lt;/p&gt;
&lt;p&gt;So if you want to support a company that treats its paying customers like criminals because they get a new computer, go right ahead and support Peaksware. But anyone who expects to use software that they pay for even if they happen to buy a new computer should steer clear.&lt;/p&gt;
&lt;p&gt;The real kicker is that all that work is locked away in their proprietary file format simply because of their draconian licensing. This is the real take home lesson to all software users (not just fitness geeks): &lt;strong&gt;If you lock your data away in a proprietary format and are beholden to a single company in order to access it, they can and will screw you. Always insist on open data formats, even if using proprietary software&lt;/strong&gt;. Oh and always read the EULA carefully before clicking OK!&lt;/p&gt;</content><category term="articles"></category></entry><entry><title>Reading XFS partition from Windows</title><link href="https://www.perrygeo.com/reading-xfs-partition-from-windows.html" rel="alternate"></link><published>2009-06-21T00:00:00-06:00</published><updated>2009-06-21T00:00:00-06:00</updated><author><name>Matthew T. Perry</name></author><id>tag:www.perrygeo.com,2009-06-21:/reading-xfs-partition-from-windows.html</id><summary type="html">&lt;p&gt;When I was setting up my linux system a few years ago, I did some research into filesystems and determined that the &lt;a href="http://en.wikipedia.org/wiki/XFS"&gt;XFS file system&lt;/a&gt;, being particularly proficient in dealing with large files, would be ideal for my home directory. And it was. But the one factor I didn't consider …&lt;/p&gt;</summary><content type="html">&lt;p&gt;When I was setting up my linux system a few years ago, I did some research into filesystems and determined that the &lt;a href="http://en.wikipedia.org/wiki/XFS"&gt;XFS file system&lt;/a&gt;, being particularly proficient in dealing with large files, would be ideal for my home directory. And it was. But the one factor I didn't consider was portability. Turns out that there is basically no support for XFS in windows. &lt;/p&gt;
&lt;p&gt;So how do you access your files from Windows if they are on an XFS partition? I had just shy of 1 TB of data to transfer so using my other linux box and transferring across the network would have taken forever. The solution I came up with is a bit convoluted but it has some real advantages:&lt;/p&gt;
&lt;p&gt;1) Install Sun's VirtualBox.
2) Download an iso for your favorite linux distribution (mine being Ubuntu 9.04)
3) Create a virtual machine from the linux iso
4) Install the VBOxGuestAdditions in the linux virtual machine. 
5) Create a Share folder on the windows host and register it with the virtual machine. This will allow you to transfer files from the guest (linux) to the host(windows) You may have to manually mount the drive in the linux guest:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;mount -t vboxsf share_name /mnt/share_name
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;6) Using the windows host cmd line, create a vmdk from the physical drive that your XFS partition resides on. In this case, PhysicalDrive1 corresponds to the second SATA connector. This will allow your guest OS to talk directly with the drive:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;C:&lt;/span&gt;&lt;span class="n"&gt;\Program&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Files\Sun\xVM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;VirtualBox&lt;/span&gt;
&lt;span class="n"&gt;VBoxManage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exe&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;internalcommands&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;createrawvmdk&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;C:\Documents and Settings\perry\.VirtualBox\HardDisks\Physical1.vmdk&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;rawdisk&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;\\.\PhysicalDrive1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;register&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Once completed, you should see:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;RAW host disk access VMDK file 
C:\Documents and Settings\perry\.VirtualBox\HardDisks\Physical1.vmdk created successfully.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;7) Make sure to add the physical drive to your list of hard drives in the linux guest options. Restart the linux guest virtual machine and your XFS partition should already be mounted. Now you can begin transfering files between your XFS partition and the shared folder on the windows host.&lt;/p&gt;
&lt;p&gt;Whew. Lots of hassle for a simple file transfer, right! But the side benefit is that now you have a fully functional linux virtual machine with a shared folder set up to the windows host. Very useful - even when you must run windows, it helps to have a linux VM standing by!&lt;/p&gt;</content><category term="articles"></category></entry><entry><title>IronPython (2.6) and ArcGIS - ready for prime time!!</title><link href="https://www.perrygeo.com/ironpython-26-and-arcgis-ready-for-prime-time.html" rel="alternate"></link><published>2009-06-16T00:00:00-06:00</published><updated>2009-06-16T00:00:00-06:00</updated><author><name>Matthew T. Perry</name></author><id>tag:www.perrygeo.com,2009-06-16:/ironpython-26-and-arcgis-ready-for-prime-time.html</id><summary type="html">&lt;p&gt;Not sure why this didn't occur to me &lt;em&gt;before&lt;/em&gt; I wrote &lt;a href="http://www.perrygeo.net/wordpress/?p=135"&gt;that last post&lt;/a&gt; but I tried the "pythonic" version of the code under the &lt;a href="http://ironpython.codeplex.com/Release/ProjectReleases.aspx?ReleaseId=25126"&gt;IronPython 2.6 Beta 1&lt;/a&gt; release and it works!&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;lyr = Carto.LayerFileClass()
lyr.Open(&amp;#39;C:\\test.lyr&amp;#39;)
print lyr.Filename
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Works perfectly now. So IronPython …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Not sure why this didn't occur to me &lt;em&gt;before&lt;/em&gt; I wrote &lt;a href="http://www.perrygeo.net/wordpress/?p=135"&gt;that last post&lt;/a&gt; but I tried the "pythonic" version of the code under the &lt;a href="http://ironpython.codeplex.com/Release/ProjectReleases.aspx?ReleaseId=25126"&gt;IronPython 2.6 Beta 1&lt;/a&gt; release and it works!&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;lyr = Carto.LayerFileClass()
lyr.Open(&amp;#39;C:\\test.lyr&amp;#39;)
print lyr.Filename
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Works perfectly now. So IronPython &lt;strong&gt;2.6&lt;/strong&gt; promises to be a viable option for extending ArcGIS. My enthusiasm has been renewed.&lt;/p&gt;</content><category term="articles"></category></entry><entry><title>IronPython and ArcGIS - not quite ready for prime time</title><link href="https://www.perrygeo.com/ironpython-and-arcgis-not-quite-ready-for-prime-time.html" rel="alternate"></link><published>2009-06-16T00:00:00-06:00</published><updated>2009-06-16T00:00:00-06:00</updated><author><name>Matthew T. Perry</name></author><id>tag:www.perrygeo.com,2009-06-16:/ironpython-and-arcgis-not-quite-ready-for-prime-time.html</id><summary type="html">&lt;p&gt;Occasionally I find myself in the C#/.NET world in order to write code using ESRI ArcObjects. Today I was toying with the idea of automating the creation of ESRI Layer files (a file which defines the cartographic styling of a dataset). Of course they are in an undocumented binary …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Occasionally I find myself in the C#/.NET world in order to write code using ESRI ArcObjects. Today I was toying with the idea of automating the creation of ESRI Layer files (a file which defines the cartographic styling of a dataset). Of course they are in an undocumented binary file format, &lt;a href="http://blog.cleverelephant.ca/2009/04/esri-formats-back-to-future.html"&gt;inaccessible to anything but ESRI software&lt;/a&gt;. So I pop open Visual Studio .... &lt;/p&gt;
&lt;p&gt;I feel a nagging unease every time I type a set of curly braces. And VB just makes me insane. I prefer, of course, to use python. Luckily there is &lt;a href="http://www.codeplex.com/Wiki/View.aspx?ProjectName=IronPython"&gt;IronPython&lt;/a&gt; which runs on .NET - which means I could theoretically use it to interact with ArcGIS. &lt;/p&gt;
&lt;p&gt;I only found a &lt;a href="http://moreati.org.uk/blog/2009/01/27/from-esriarcgis-import-geodatabase/"&gt;single working example&lt;/a&gt; of using ArcObjects through IronPython. But it looked promising enough to close Visual Studio and give it a go. &lt;/p&gt;
&lt;p&gt;The first nagging problem is an IronPython-specific one. Relatively minor annoyance but you have to add the reference to a .NET assembly (library) before you can load it. &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;clr&lt;/span&gt;
&lt;span class="n"&gt;clr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddReference&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;ESRI.ArcGIS.System&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;clr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddReference&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;ESRI.ArcGIS.Carto&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;ESRI.ArcGIS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;esriSystem&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;ESRI.ArcGIS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Carto&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Now there is the issue of grabbing an ESRI license. A little verbose IMO but it could easily be encapsulated in a helper function to clean things up. &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nv"&gt;aoc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;esriSystem&lt;/span&gt;.&lt;span class="nv"&gt;AoInitializeClass&lt;/span&gt;&lt;span class="ss"&gt;()&lt;/span&gt;
&lt;span class="nv"&gt;res&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;esriSystem&lt;/span&gt;.&lt;span class="nv"&gt;IAoInitialize&lt;/span&gt;.&lt;span class="nv"&gt;IsProductCodeAvailable&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;aoc&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="nv"&gt;esriSystem&lt;/span&gt;.&lt;span class="nv"&gt;esriLicenseProductCode&lt;/span&gt;.&lt;span class="nv"&gt;esriLicenseProductCodeArcView&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;res&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;esriSystem&lt;/span&gt;.&lt;span class="nv"&gt;esriLicenseStatus&lt;/span&gt;.&lt;span class="nv"&gt;esriLicenseAvailable&lt;/span&gt;:
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;esriSystem&lt;/span&gt;.&lt;span class="nv"&gt;IAoInitialize&lt;/span&gt;.&lt;span class="nv"&gt;Initialize&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;aoc&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nv"&gt;esriSystem&lt;/span&gt;.&lt;span class="nv"&gt;esriLicenseProductCode&lt;/span&gt;.&lt;span class="nv"&gt;esriLicenseProductCodeArcView&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Now that we've satisfied the demands of our proprietary license overlords, we can proceed with the real work .. in this case I just want to open an existing Layer file and see if the resulting object knows it's own file path. Really simple, right?&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;lyr&lt;span class="w"&gt; &lt;/span&gt;=&lt;span class="w"&gt; &lt;/span&gt;Carto.LayerFileClass()
if&lt;span class="w"&gt; &lt;/span&gt;&amp;quot;Open&amp;quot;&lt;span class="w"&gt; &lt;/span&gt;in&lt;span class="w"&gt; &lt;/span&gt;dir(lyr):&lt;span class="w"&gt; &lt;/span&gt;print&lt;span class="w"&gt; &lt;/span&gt;&amp;quot;The&lt;span class="w"&gt; &lt;/span&gt;Layer&lt;span class="w"&gt; &lt;/span&gt;object&lt;span class="w"&gt; &lt;/span&gt;has&lt;span class="w"&gt; &lt;/span&gt;an&lt;span class="w"&gt; &lt;/span&gt;Open&lt;span class="w"&gt; &lt;/span&gt;method&lt;span class="w"&gt; &lt;/span&gt;but....&amp;quot;
lyr.Open(&amp;#39;C:\\test.lyr&amp;#39;)
print&lt;span class="w"&gt; &lt;/span&gt;lyr.Filename



The&lt;span class="w"&gt; &lt;/span&gt;Layer&lt;span class="w"&gt; &lt;/span&gt;object&lt;span class="w"&gt; &lt;/span&gt;has&lt;span class="w"&gt; &lt;/span&gt;an&lt;span class="w"&gt; &lt;/span&gt;Open&lt;span class="w"&gt; &lt;/span&gt;method&lt;span class="w"&gt; &lt;/span&gt;but....
Traceback&lt;span class="w"&gt; &lt;/span&gt;(most&lt;span class="w"&gt; &lt;/span&gt;recent&lt;span class="w"&gt; &lt;/span&gt;call&lt;span class="w"&gt; &lt;/span&gt;last):
&lt;span class="w"&gt; &lt;/span&gt;File&lt;span class="w"&gt; &lt;/span&gt;&amp;quot;&lt;span class="nt"&gt;&amp;lt;stdin&amp;gt;&lt;/span&gt;&amp;quot;,&lt;span class="w"&gt; &lt;/span&gt;line&lt;span class="w"&gt; &lt;/span&gt;1,&lt;span class="w"&gt; &lt;/span&gt;in&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;module&amp;gt;&lt;/span&gt;
AttributeError:&lt;span class="w"&gt; &lt;/span&gt;&amp;#39;GenericComObject&amp;#39;&lt;span class="w"&gt; &lt;/span&gt;object&lt;span class="w"&gt; &lt;/span&gt;has&lt;span class="w"&gt; &lt;/span&gt;no&lt;span class="w"&gt; &lt;/span&gt;attribute&lt;span class="w"&gt; &lt;/span&gt;&amp;#39;Open&amp;#39;&lt;span class="nt"&gt;&amp;lt;/module&amp;gt;&amp;lt;/stdin&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Hrm. Looks like we've run across &lt;a href="http://www.codeplex.com/IronPython/WorkItem/View.aspx?WorkItemId=1506"&gt;bug 1506&lt;/a&gt; which doesn't allow access to the properties and methods of a given instance - instead your have to work through the functions provided by the implementation. Grr...&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;Carto.ILayerFile.Open(lyr, &amp;#39;C:\\test.lyr&amp;#39;)
print Carto.ILayerFile.Filename.GetValue(lyr)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;That is unwieldy, ugly and &lt;a href="http://shalabh.infogami.com/Be_Pythonic2"&gt;unpythonic&lt;/a&gt;. What's the point of object oriented programming if you can't access the methods and properties of an object directly? Since all ArcObjects applications are based on extending COM interfaces, this would be a major pain in any non-trivial application. Basically, until these .NET-accessible COM objects can be treated in a pythonic way,  I don't see any compelling reason to pursue IronPython and ArcGIS integration. Looks like its back to C# for the moment ... (/me take a deep sigh and opens Visual Studio)  ... unless of course anyone has some brilliant solution to share!!&lt;/p&gt;</content><category term="articles"></category></entry><entry><title>The GPS told me to do it</title><link href="https://www.perrygeo.com/the-gps-told-me-to-do-it.html" rel="alternate"></link><published>2009-06-12T00:00:00-06:00</published><updated>2009-06-12T00:00:00-06:00</updated><author><name>Matthew T. Perry</name></author><id>tag:www.perrygeo.com,2009-06-12:/the-gps-told-me-to-do-it.html</id><summary type="html">&lt;p&gt;Another disastrous consequence of inaccurate spatial information... Not only can you accidentally &lt;a href="http://www.perrygeo.net/wordpress/?p=75"&gt;tag your neighbor as a criminal&lt;/a&gt;, now it appears that sloppy spatial data has lead to &lt;a href="http://www.wsbtv.com/news/19715994/detail.html"&gt;the wrong house getting demolished&lt;/a&gt;.  &lt;/p&gt;
&lt;p&gt;I've asked it before but its worth repeating ... with all the recent advances in spatial data publishing …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Another disastrous consequence of inaccurate spatial information... Not only can you accidentally &lt;a href="http://www.perrygeo.net/wordpress/?p=75"&gt;tag your neighbor as a criminal&lt;/a&gt;, now it appears that sloppy spatial data has lead to &lt;a href="http://www.wsbtv.com/news/19715994/detail.html"&gt;the wrong house getting demolished&lt;/a&gt;.  &lt;/p&gt;
&lt;p&gt;I've asked it before but its worth repeating ... with all the recent advances in spatial data publishing, where are the advances in metadata and data quality assurance? How do you know where the data comes from, what's been done to it and by whom? What is the intended use of the data? For the vast majority of the data being shoved out onto the web, these bits of metadata are sorely lacking.&lt;/p&gt;
&lt;p&gt;Of course this case is more a matter of one person's sheer stupidity; I'm not sure any caveats in the metadata would have stopped the wrecking ball!&lt;/p&gt;</content><category term="articles"></category></entry><entry><title>The magic bullet</title><link href="https://www.perrygeo.com/the-magic-bullet.html" rel="alternate"></link><published>2009-03-25T00:00:00-06:00</published><updated>2009-03-25T00:00:00-06:00</updated><author><name>Matthew T. Perry</name></author><id>tag:www.perrygeo.com,2009-03-25:/the-magic-bullet.html</id><summary type="html">&lt;p&gt;Dealing with corrupted shapefiles can be a painful experience: programs crash for seemingly no reason, attribute tables get screwy, features get lost, queries results don't look right and ArcGIS processing tools fail with mysterious error codes:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Dissolve error" src="/assets/img/uploads/2009/03/dissolve_error.jpg"&gt;&lt;/p&gt;
&lt;p&gt;Never fear, OGR is here. The magic bullet for fixing corrupted shapefiles is, 90 …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Dealing with corrupted shapefiles can be a painful experience: programs crash for seemingly no reason, attribute tables get screwy, features get lost, queries results don't look right and ArcGIS processing tools fail with mysterious error codes:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Dissolve error" src="/assets/img/uploads/2009/03/dissolve_error.jpg"&gt;&lt;/p&gt;
&lt;p&gt;Never fear, OGR is here. The magic bullet for fixing corrupted shapefiles is, 90% of the time, accomplished by using ogr2ogr to convert the shapefile to another shapefile. &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;ogr2ogr -f &amp;quot;ESRI Shapefile&amp;quot;  shiny_new_clean_dataset.shp corrupted_dataset.shp corrupted_dataset
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;OGR's internal data model cleans it up and the output is a fresh shiny new shapefile that works without hassle. &lt;/p&gt;</content><category term="articles"></category><category term="ogr"></category></entry><entry><title>TV cycling coverage is dead</title><link href="https://www.perrygeo.com/tv-cycling-coverage-is-dead.html" rel="alternate"></link><published>2009-02-19T00:00:00-07:00</published><updated>2009-02-19T00:00:00-07:00</updated><author><name>Matthew T. Perry</name></author><id>tag:www.perrygeo.com,2009-02-19:/tv-cycling-coverage-is-dead.html</id><summary type="html">&lt;p&gt;Real-time spatial application developers take note...&lt;/p&gt;
&lt;p&gt;I've been following the Tour of California this week (looking forward to the Solvang Time Trial this Friday) and have been disappointed with the TV coverage on Versus. Its not that the coverage is bad, its just that long-distance endurance sports don't lend themselves …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Real-time spatial application developers take note...&lt;/p&gt;
&lt;p&gt;I've been following the Tour of California this week (looking forward to the Solvang Time Trial this Friday) and have been disappointed with the TV coverage on Versus. Its not that the coverage is bad, its just that long-distance endurance sports don't lend themselves to the traditional 2 announcers and 1 camera format. There are multiple groups of riders and so much spatial information to keep track of if one really wants to understand the dynamics of a cycling event.&lt;/p&gt;
&lt;p&gt;Maybe I've just been spoiled by the &lt;a href="http://tracker.amgentourofcalifornia.com/"&gt;Amgen Tour Tracker&lt;/a&gt;. It is a crowning example of a spatially-aware real-time web application.&lt;/p&gt;
&lt;p&gt;&lt;a href="/assets/img/tour_tracker.png"&gt;&lt;img alt="" src="/assets/img/tour_tracker_thumb.jpg"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;It provides two cameras of live coverage, live commentary with interviews, chat, summary updates, gps tracking of riders shown on both an elevation profile and a yahoo-based aerial map, "gps+" location prediction, race standings, time checks, etc. Far more information than any TV coverage without resorting to information overload. &lt;/p&gt;</content><category term="articles"></category><category term="cycling"></category><category term="interface"></category></entry><entry><title>Stimulus watch</title><link href="https://www.perrygeo.com/stimulus-watch.html" rel="alternate"></link><published>2009-02-12T00:00:00-07:00</published><updated>2009-02-12T00:00:00-07:00</updated><author><name>Matthew T. Perry</name></author><id>tag:www.perrygeo.com,2009-02-12:/stimulus-watch.html</id><summary type="html">&lt;p&gt;Last time I posted on this blog, Hillary and Obama were still battling it out for the Democratic nomination. Now Barack Obama is our president with an uphill battle to save the economy. So yeah, it's been a while. I haven't been doing too much innovative Geo-related stuff lately, hence …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Last time I posted on this blog, Hillary and Obama were still battling it out for the Democratic nomination. Now Barack Obama is our president with an uphill battle to save the economy. So yeah, it's been a while. I haven't been doing too much innovative Geo-related stuff lately, hence the lack of blog posts. I'll try to pick up the pace a bit, even if I have to resort to fluff pieces like this one...&lt;/p&gt;
&lt;p&gt;Well, it looks like the economic stimulus bill is going to pass. The bill doesn’t actually specify the projects that will be funded; the money will be allocated to cities and some federal grant agencies. The mayors have already proposed thousands of “shovel-ready” projects that might get a green light depending on how much funding the city gets.&lt;/p&gt;
&lt;p&gt;There’s a great site, &lt;a href="http://www.stimuluswatch.org"&gt;stimuluswatch.org&lt;/a&gt;, that allows the public to review these proposals. Good to know where our tax dollars are headed!&lt;/p&gt;
&lt;p&gt;There are several &lt;a href="http://www.stimuluswatch.org/project/search/GIS"&gt;GIS proposals&lt;/a&gt; ranging from projects with specific, well-defined (and measurable) objectives to the nebulous "Give us $500,000 to upgrade our cities' GIS program".  It will be interesting to see which ones pan out, which ones produce results and which ones are just a pure waste of taxpayer dollars. &lt;/p&gt;
&lt;p&gt;P.S. If you'd like to see where most of my time and energy is going these days, it's training for the US National Cup mountain bike race series. My &lt;a href="http://viedevelo.wordpress.com/"&gt;cycling exploits are available for all&lt;/a&gt; who are inclined to read them.&lt;/p&gt;</content><category term="articles"></category></entry><entry><title>R is for Radiohead</title><link href="https://www.perrygeo.com/r-is-for-radiohead.html" rel="alternate"></link><published>2008-07-15T00:00:00-06:00</published><updated>2008-07-15T00:00:00-06:00</updated><author><name>Matthew T. Perry</name></author><id>tag:www.perrygeo.com,2008-07-15:/r-is-for-radiohead.html</id><summary type="html">&lt;p&gt;Radiohead realeased their video for &lt;a href="http://code.google.com/creative/radiohead/"&gt;House of Cards&lt;/a&gt; yesterday. Besides being a big radiohead fan, I was also loving the &lt;a href="http://www.velodyne.com/lidar/"&gt;LIDAR &lt;/a&gt;&lt;a href="http://www.geometricinformatics.com/"&gt;technology &lt;/a&gt;behind the video. &lt;/p&gt;
&lt;p&gt;If you want to check it out yourself, there are code samples on the site as well as access to the raw data. The csv …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Radiohead realeased their video for &lt;a href="http://code.google.com/creative/radiohead/"&gt;House of Cards&lt;/a&gt; yesterday. Besides being a big radiohead fan, I was also loving the &lt;a href="http://www.velodyne.com/lidar/"&gt;LIDAR &lt;/a&gt;&lt;a href="http://www.geometricinformatics.com/"&gt;technology &lt;/a&gt;behind the video. &lt;/p&gt;
&lt;p&gt;If you want to check it out yourself, there are code samples on the site as well as access to the raw data. The csv files have four columns (x, y, z, and intensity). For me the quickest way to visualize the data was through R and it's OpenGL interface called rgl (which is a wonderful high-level 3D data visualization environment). &lt;/p&gt;
&lt;p&gt;Assuming you have R installed, rgl is a simple add on through the CRAN repositories:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;install&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;packages&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;rgl&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Then you need to load the library, load the csv, scale the intensity values from 0 to 1. Then it's a simple rgl.points command to get an interactive 3D rendering:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nf"&gt;library&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rgl&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;read.csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;C:/temp/radiohead/22.csv&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;header&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;FALSE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# scale intensity values from 0 to 1&lt;/span&gt;
&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="n"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;[,&lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;255&lt;/span&gt;

&lt;span class="c1"&gt;# rgl.points(x,y,z,size=__,color=__)&lt;/span&gt;
&lt;span class="c1"&gt;# note y value is inverted&lt;/span&gt;
&lt;span class="c1"&gt;# color is a grayscale rgb based on intensity&lt;/span&gt;
&lt;span class="nf"&gt;rgl.points&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;[,&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;[,&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="m"&gt;-1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;[,&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;rgb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="n"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="n"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="n"&gt;int&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;That's all it takes to render Thom Yorke in all his 3D digital glory:&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="/assets/img/radiohead2.jpg"&gt;&lt;/p&gt;</content><category term="articles"></category><category term="r"></category><category term="lidar"></category></entry><entry><title>Geospatial Reddit - 2 weeks later</title><link href="https://www.perrygeo.com/geospatial-reddit-2-weeks-later.html" rel="alternate"></link><published>2008-06-12T00:00:00-06:00</published><updated>2008-06-12T00:00:00-06:00</updated><author><name>Matthew T. Perry</name></author><id>tag:www.perrygeo.com,2008-06-12:/geospatial-reddit-2-weeks-later.html</id><summary type="html">&lt;p&gt;So, despite frustrations with getting submitted URLs to appear, &lt;a href="http://www.reddit.com/r/geospatial/"&gt;Geospatial Reddit&lt;/a&gt; is still puttering along. Not exactly a vibrant community &lt;em&gt;yet&lt;/em&gt; but there are currently 133 subscribers. If you're subscribed, take a minute to submit your favorite URLs. If you haven't subscribed, &lt;a href="http://www.reddit.com/r/geospatial/"&gt;check it out&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I thought 133 subscribers was …&lt;/p&gt;</summary><content type="html">&lt;p&gt;So, despite frustrations with getting submitted URLs to appear, &lt;a href="http://www.reddit.com/r/geospatial/"&gt;Geospatial Reddit&lt;/a&gt; is still puttering along. Not exactly a vibrant community &lt;em&gt;yet&lt;/em&gt; but there are currently 133 subscribers. If you're subscribed, take a minute to submit your favorite URLs. If you haven't subscribed, &lt;a href="http://www.reddit.com/r/geospatial/"&gt;check it out&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I thought 133 subscribers was a decent number until I found that the &lt;a href="http://www.reddit.com/r/bacon/"&gt;Bacon subreddit&lt;/a&gt; has over 500. Apparently the world would rather discuss their greasy breakfast food than maps. &lt;/p&gt;</content><category term="articles"></category></entry><entry><title>Jabref - Open Source Alternative to EndNote</title><link href="https://www.perrygeo.com/jabref-open-source-alternative-to-endnote.html" rel="alternate"></link><published>2008-06-08T00:00:00-06:00</published><updated>2008-06-08T00:00:00-06:00</updated><author><name>Matthew T. Perry</name></author><id>tag:www.perrygeo.com,2008-06-08:/jabref-open-source-alternative-to-endnote.html</id><summary type="html">&lt;p&gt;For those of you that use EndNote to keep track of your bibliographies/references , there is an alternative. &lt;a href="http://jabref.sourceforge.net"&gt;JabRef&lt;/a&gt;. I find the &lt;a href="http://jabref.sourceforge.net/images/Jabref-ScreenShot-MainWindow.png"&gt;UI&lt;/a&gt; to be very intuitive and it has a range of customizable import/export formats. JabRef uses the &lt;a href="http://en.wikipedia.org/wiki/BibTeX"&gt;BibTex&lt;/a&gt; format as it's native file format so, of course …&lt;/p&gt;</summary><content type="html">&lt;p&gt;For those of you that use EndNote to keep track of your bibliographies/references , there is an alternative. &lt;a href="http://jabref.sourceforge.net"&gt;JabRef&lt;/a&gt;. I find the &lt;a href="http://jabref.sourceforge.net/images/Jabref-ScreenShot-MainWindow.png"&gt;UI&lt;/a&gt; to be very intuitive and it has a range of customizable import/export formats. JabRef uses the &lt;a href="http://en.wikipedia.org/wiki/BibTeX"&gt;BibTex&lt;/a&gt; format as it's native file format so, of course, it integrates very well with &lt;a href="http://en.wikipedia.org/wiki/LaTeX"&gt;LaTeX&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;One of the neat features is the ability to create custom bibliographies in HTML, complete with javascript-based search capabilities. Here's &lt;a href="http://perrygeo.net/references.html"&gt;my reference list&lt;/a&gt; which I'll be slowly adding to as I convert all my old text-based and EndNote reference lists over. &lt;/p&gt;</content><category term="articles"></category></entry><entry><title>Geospatial Reddit - A democratic solution to geo blog overload?</title><link href="https://www.perrygeo.com/geospatial-reddit-a-democratic-solution-to-geo-blog-overload.html" rel="alternate"></link><published>2008-05-28T00:00:00-06:00</published><updated>2008-05-28T00:00:00-06:00</updated><author><name>Matthew T. Perry</name></author><id>tag:www.perrygeo.com,2008-05-28:/geospatial-reddit-a-democratic-solution-to-geo-blog-overload.html</id><summary type="html">&lt;p&gt;All the great GIS news/blog aggregators out there (planetgs, slashgeo, etc) are moderator driven - a few people act as the gatekeepers and inevitably &lt;a href="http://www.spatiallyadjusted.com/2008/05/23/planet-geospatial-reboot-coming/"&gt;have to decide what information is useful&lt;/a&gt;. This is &lt;a href="http://zcologia.com/news/762/planet-geospatial/"&gt;not the ideal way&lt;/a&gt; to do things. &lt;/p&gt;
&lt;p&gt;There's a more democratic and distributed way to spread the …&lt;/p&gt;</summary><content type="html">&lt;p&gt;All the great GIS news/blog aggregators out there (planetgs, slashgeo, etc) are moderator driven - a few people act as the gatekeepers and inevitably &lt;a href="http://www.spatiallyadjusted.com/2008/05/23/planet-geospatial-reboot-coming/"&gt;have to decide what information is useful&lt;/a&gt;. This is &lt;a href="http://zcologia.com/news/762/planet-geospatial/"&gt;not the ideal way&lt;/a&gt; to do things. &lt;/p&gt;
&lt;p&gt;There's a more democratic and distributed way to spread the role - it's called &lt;em&gt;reddit&lt;/em&gt;. &lt;img alt="" src="http://reallystatic.reddit.com/static/create-a-reddit.png"&gt;  More specifically, &lt;a href="http://reddit.com/r/geospatial"&gt;Geospatial Reddit&lt;/a&gt;. For those unfamiliar with reddit (or similar sites like digg), the idea is simple: users submit stories and users vote on stories. The most popular ones rise to the top and, theoretically, the best articles magically appear on the front page. Much like democracy itself, there are flaws in the theory but its the best thing we've got.&lt;/p&gt;
&lt;p&gt;Geospatial Reddit is public so sign up, submit your favorite stories and vote. Lets see if we can make this work.&lt;/p&gt;</content><category term="articles"></category></entry><entry><title>Posting to Geospatial Reddit</title><link href="https://www.perrygeo.com/posting-to-geospatial-reddit.html" rel="alternate"></link><published>2008-05-28T00:00:00-06:00</published><updated>2008-05-28T00:00:00-06:00</updated><author><name>Matthew T. Perry</name></author><id>tag:www.perrygeo.com,2008-05-28:/posting-to-geospatial-reddit.html</id><summary type="html">&lt;p&gt;Some folks have had trouble submitting links so I figured I should post a bit more detail on that. To get articles to show up on the &lt;em&gt;geospatial&lt;/em&gt; reddit (not the main reddit), go to &lt;a href="http://reddit.com/r/geospatial/submit"&gt;http://reddit.com/r/geospatial/submit&lt;/a&gt; or click the "Submit a Link" button on the …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Some folks have had trouble submitting links so I figured I should post a bit more detail on that. To get articles to show up on the &lt;em&gt;geospatial&lt;/em&gt; reddit (not the main reddit), go to &lt;a href="http://reddit.com/r/geospatial/submit"&gt;http://reddit.com/r/geospatial/submit&lt;/a&gt; or click the "Submit a Link" button on the right - from the geospatial page. When you're submitting the url, you should see "submit to geospatial" as the page header. &lt;/p&gt;
&lt;p&gt;I know at least 2 of us have been successful at posting. If this doesn't work for you, please let me know and I'll try and figure it out. &lt;/p&gt;</content><category term="articles"></category></entry><entry><title>So you want to learn to learn about kriging …</title><link href="https://www.perrygeo.com/so-you-want-to-learn-to-learn-about-kriging.html" rel="alternate"></link><published>2008-05-25T00:00:00-06:00</published><updated>2008-05-25T00:00:00-06:00</updated><author><name>Matthew T. Perry</name></author><id>tag:www.perrygeo.com,2008-05-25:/so-you-want-to-learn-to-learn-about-kriging.html</id><summary type="html">&lt;p&gt;Guides like &lt;a href="http://spatial-analyst.net/"&gt;Tomislav Hengl's&lt;/a&gt; &lt;a href="http://eusoils.jrc.it/ESDB_Archive/eusoils_docs/other/EUR22904en.pdf"&gt;Practical Guide to Geostatistical Mapping of Environmental Variables&lt;/a&gt; and Rossiter's &lt;a href="http://www.itc.nl/~rossiter/teach/stats/ssi_short.pdf"&gt;Introduction to applied geostatistics&lt;/a&gt; do an excellent job of providing a grounded, relatively easy to understand, introduction to geostatical prediction and kriging.&lt;/p&gt;
&lt;p&gt;But if you're an experience learner (like me) you don't absorb the mathematics fully …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Guides like &lt;a href="http://spatial-analyst.net/"&gt;Tomislav Hengl's&lt;/a&gt; &lt;a href="http://eusoils.jrc.it/ESDB_Archive/eusoils_docs/other/EUR22904en.pdf"&gt;Practical Guide to Geostatistical Mapping of Environmental Variables&lt;/a&gt; and Rossiter's &lt;a href="http://www.itc.nl/~rossiter/teach/stats/ssi_short.pdf"&gt;Introduction to applied geostatistics&lt;/a&gt; do an excellent job of providing a grounded, relatively easy to understand, introduction to geostatical prediction and kriging.&lt;/p&gt;
&lt;p&gt;But if you're an experience learner (like me) you don't absorb the mathematics fully without &lt;em&gt;doing&lt;/em&gt; something with the knowledge; Seeing it in action brings the concepts to life. Unfortunately most geostats/kriging software is either too complex for exploratory learning (not enough immediate feedback) or too simplistic (making too many assumptions, disallowing access to the nitty-gritty details). Either way, you're bound to produce output with fundamental flaws because you're not aware of the finer details of variogram modelling. I speak from exerience!&lt;/p&gt;
&lt;p&gt;Luckily Dennis J. J. Walvoort of the Wageningen University &amp;amp; Research Center saw the same problem and created an nifty learning to to explore varigoram models and spatial predictions using ordinary kriging - &lt;a href="http://www.ai-geostats.org/index.php?id=114"&gt;EZ-Kriging&lt;/a&gt;. No degree in math or statistical theory required. Just drag the points around, play with the parameters and alter the underlying data as a table and see the results immediately.&lt;/p&gt;
&lt;p&gt;&lt;a href="/assets/img/ezkriging.jpg"&gt;&lt;img alt="" src="/assets/img/ezkriging_thumb.jpg"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Its nothing more than a simulation so don't expect to load your own datasets or produce any meaningful output with it. But it truly excels as a learning tool to understand the core concepts behind kriging and is a great complement to Hengl and Rossiter's work. With that knowledge you can do the real deal in Surfer, R, ILWIS or your geostats software of choice.&lt;/p&gt;
&lt;p&gt;EDIT: One complaint about this EZ-Kriging that I have: it doesn't show the observed sample variogram cloud overlayed on the variogram model. Oh well still a nice tool.&lt;/p&gt;
&lt;p&gt;EDIT2: It's a windows .exe but it runs smoothly under wine in linux.&lt;/p&gt;</content><category term="articles"></category><category term="geostatistics"></category><category term="kriging"></category></entry><entry><title>Ubuntu as a GIS workstation (updated for Hardy Heron)</title><link href="https://www.perrygeo.com/ubuntu-as-a-gis-workstation-updated-for-hardy-heron.html" rel="alternate"></link><published>2008-05-14T00:00:00-06:00</published><updated>2008-05-14T00:00:00-06:00</updated><author><name>Matthew T. Perry</name></author><id>tag:www.perrygeo.com,2008-05-14:/ubuntu-as-a-gis-workstation-updated-for-hardy-heron.html</id><summary type="html">&lt;p&gt;As a followup to my previous post on &lt;a href="http://www.perrygeo.net/wordpress/?p=10"&gt;turning Ubuntu Gutsy into a GIS workstation&lt;/a&gt;, Here are the revised instructions for Ubuntu 8.04 (The Hardy Heron). &lt;/p&gt;
&lt;p&gt;Note that there are a few additonal apps and changes in here:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Postgis&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Mapnik&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;New version of QGIS installed via repository&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;OpenStreetMap tools …&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;</summary><content type="html">&lt;p&gt;As a followup to my previous post on &lt;a href="http://www.perrygeo.net/wordpress/?p=10"&gt;turning Ubuntu Gutsy into a GIS workstation&lt;/a&gt;, Here are the revised instructions for Ubuntu 8.04 (The Hardy Heron). &lt;/p&gt;
&lt;p&gt;Note that there are a few additonal apps and changes in here:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Postgis&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Mapnik&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;New version of QGIS installed via repository&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;OpenStreetMap tools (JOSM and osm2pgsql)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Geotiff utilities&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Some nice python spatial libs (shapely, owslib, geopy and pyproj) &lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Run the following as root on your new Hardy installation, answer a few configuration questions and you'll be ready to go.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;deb http://ppa.launchpad.net/qgis/ubuntu hardy main&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;etc&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;apt&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;sources&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;list&lt;/span&gt;

&lt;span class="n"&gt;apt&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;update&lt;/span&gt;

&lt;span class="n"&gt;apt&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;force&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;yes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;install&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;grass&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;mapserver&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;bin&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;\
&lt;span class="n"&gt;gdal&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;bin&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cgi&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;mapserver&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;python&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;qt4&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;python&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;sip4&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;python&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;gdal&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;\
&lt;span class="n"&gt;python&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;mapscript&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;gmt&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;gmt&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;coastline&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;recommended&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;gpsbabel&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;\
&lt;span class="n"&gt;shapelib&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;qgis&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;qgis&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;plugin&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;grass&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;python&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;setuptools&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;\
&lt;span class="n"&gt;python&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;mapnik&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;mapnik&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;plugins&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;mapnik&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;utils&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;osm2pgsql&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;josm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;postgresql&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;8.3&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;postgis&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;\
&lt;span class="n"&gt;python&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;dev&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;build&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;essential&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;libgdal&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;dev&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;geotiff&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;bin&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sun&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;java6&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;jre&lt;/span&gt;

&lt;span class="n"&gt;easy_install&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;shapely&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;geopy&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;owslib&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pyproj&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;EDIT: If you're looking for more up to date packages for geos, gdal, etc, try adding &lt;code&gt;deb http://les-ejk.cz/ubuntu/ hardy multiverse&lt;/code&gt; to your /etc/apt/sources.list &lt;/p&gt;</content><category term="articles"></category></entry><entry><title>'Hike of Doom #2- OGC KML'</title><link href="https://www.perrygeo.com/hike-of-doom-2-ogc-kml.html" rel="alternate"></link><published>2008-04-21T00:00:00-06:00</published><updated>2008-04-21T00:00:00-06:00</updated><author><name>Matthew T. Perry</name></author><id>tag:www.perrygeo.com,2008-04-21:/hike-of-doom-2-ogc-kml.html</id><summary type="html">&lt;p&gt;In commemoration of the &lt;a href="http://www.opengeospatial.org/pressroom/pressreleases/857"&gt;OGC approval of KML&lt;/a&gt; as an open standard to share geographic content over the web, I'd like to share our recent &lt;a href="/assets/img/hikeofdoom2/hikeofdoom_20080413.kmz"&gt;"Hike of Doom #2"&lt;/a&gt; (kml provided by Mark Dotson).&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;The first weekend to hit 90 degrees, my friends and I travel inland to dive and …&lt;/p&gt;</summary><content type="html">&lt;p&gt;In commemoration of the &lt;a href="http://www.opengeospatial.org/pressroom/pressreleases/857"&gt;OGC approval of KML&lt;/a&gt; as an open standard to share geographic content over the web, I'd like to share our recent &lt;a href="/assets/img/hikeofdoom2/hikeofdoom_20080413.kmz"&gt;"Hike of Doom #2"&lt;/a&gt; (kml provided by Mark Dotson).&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;The first weekend to hit 90 degrees, my friends and I travel inland to dive and swim in the Santa Ynez river. It is billed as a "30 minute" hike to our favorite watering hole. It becomes much more than that. &lt;/p&gt;
&lt;p&gt;Of course the road leading up to the trailhead is closed due to construction so we have 3 miles of hiking on pavement just to get to the former trailhead- the Red Rocks parking lot. 
&lt;img alt="" src="/assets/img/hikeofdoom2/IMGP5144.jpg"&gt; &lt;/p&gt;
&lt;p&gt;Then the fun begins. A decent rainy season and some dam releases make for high flows and we've got half-dozen major river crossings to contend with. The recent fires added a good deal of organic matter to the river and the algae has bloomed accordingly. It is a wet, hot, rocky and slimy hike. &lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="/assets/img/hikeofdoom2/IMGP5178.jpg"&gt;&lt;/p&gt;
&lt;p&gt;We make it to the swimming hole and enjoy the day. We dive, laugh, have a few beers.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="/assets/img/hikeofdoom2/IMGP5199.jpg"&gt;
&lt;img alt="" src="/assets/img/hikeofdoom2/IMGP5206.jpg"&gt; &lt;/p&gt;
&lt;p&gt;The sun sets and the fun _ really _ begins. &lt;/p&gt;
&lt;p&gt;Klaus, the Bavarian cyclist whom we'd met at the swimming hole, met up with us just after my girlfriend, Joselyne, sprained her ankle on a rock. Her ankle hadn't started to swell yet but I could tell, drawing from my basketball injuries from the past, that she was not putting weight on it any time soon. We fashioned crutches from some driftwood. We met up with some turkey hunters (dressed in more camouflage more effective than most military uniforms) who helped us out by providing us some ankle wrap. 
&lt;img alt="" src="/assets/img/hikeofdoom2/IMGP5254.jpg"&gt; &lt;/p&gt;
&lt;p&gt;David and Andy began the trek back to the car to get help. The rest of us could either go back via the river bed , a rocky and treacherous endeavor given the setting sun, or head up to the main road and get some help. We decided on the main road and Shaun took off to alert the others to our plans. The main fire road was a trek in the _ opposite_ direction - longer, more elevation changes but smooth enough for a bike or truck and more accessible to vehicles. &lt;/p&gt;
&lt;p&gt;I carried Jos, over my shoulder fireman-style and/or piggy-back, over the river crossings.
&lt;img alt="" src="/assets/img/hikeofdoom2/IMGP5256.jpg"&gt; 
On the flats, Mark and I pushed Jos on Klaus' bike. 
&lt;img alt="" src="/assets/img/hikeofdoom2/IMGP5261.jpg"&gt;&lt;/p&gt;
&lt;p&gt;We pushed on up the trail until we reached the main road. Klaus, after drinking the last of our beer, biked up to the dam keeper's residence at Gibraltar Dam while Christina, Sarah, Mark, Jos and I  continued up the trail. A half-hour later, Klaus and the dam keeper arrived in a pickup and drove the rest of us back to the Red Rock "parking lot". 
&lt;img alt="" src="/assets/img/hikeofdoom2/IMGP5268.jpg"&gt; &lt;/p&gt;
&lt;p&gt;But the construction and rebar on the causeway meant there was no way to cross with a normal vehicle so we went by foot. Jos got back on Klaus' bike and we pushed. 
&lt;img alt="" src="/assets/img/hikeofdoom2/IMGP5274.jpg"&gt;&lt;/p&gt;
&lt;p&gt;Luckily the slight downhill grade allowed her to glide back for a good portion, graciously sparing Mark and I from permanent back injury.&lt;/p&gt;
&lt;p&gt;Meanwhile the away team had gotten some semblance of cellular reception and attempted to call the authorities. The goal was to get a ranger truck to drive out to get us or at least unlock the gate to meet us half way at the Red Rock parking lot. The authorities response was fantastic if not a bit overzealous. By the time we had gotten within a 1/4 mile of our car, we spotted helicopters. Then a firetruck. Then an ambulance. Joselyne was coasting by on Klaus' bike and they didn't even stop for her on the first pass! Apparently expecting to rescue a mangled body from the wilderness, the EMTs were somewhat disappointed at the less challenging situation they faced - a girl, coasting down the road on a bike with a sprained ankle. 
&lt;img alt="" src="/assets/img/hikeofdoom2/IMGP5279.jpg"&gt;&lt;/p&gt;
&lt;p&gt;We were back in the car, on the road before dark and got home in time for pizza.&lt;/p&gt;
&lt;p&gt;So what did we learn from this? Well as a Boy Scout, I am ashamed to say I wasn't prepared. A well prepped emergency kit would have helped a lot. At least we had an LED headlamp. Some rope would have gone a long way towards making a stretcher. An instant-ice-pack, ankle wrap and some ibuprofen would have been handy. We were wet and the mercury was falling quickly; some emergency shelter and clothing would have assuaged my concerns about the nighttime chill.&lt;/p&gt;
&lt;p&gt;But this was offset by the generosity of the many people we met for the first time - The hunters who lent us their medical supplies, the dam keeper who got up from his Sunday dinner to make sure we got back safely, the EMTs who put tremendous resources into organizing a military-scale search party, Klauss who so generously stuck with us and shared with us his bike, his wisdom and his company. Without their help and our group of friends, the story might have a less happy ending. &lt;/p&gt;
&lt;p&gt;Never underestimate the power of human kindness, generosity and cooperation! And never believe me when I say it's a short hike.&lt;/p&gt;</content><category term="articles"></category></entry><entry><title>A quick Cython introduction</title><link href="https://www.perrygeo.com/a-quick-cython-introduction.html" rel="alternate"></link><published>2008-04-19T00:00:00-06:00</published><updated>2008-04-19T00:00:00-06:00</updated><author><name>Matthew T. Perry</name></author><id>tag:www.perrygeo.com,2008-04-19:/a-quick-cython-introduction.html</id><summary type="html">&lt;p&gt;I love python for its beautiful code and practicality. But it's not going to win a &lt;a href="http://shootout.alioth.debian.org/debian/benchmark.php?test=all&amp;amp;lang=python&amp;amp;lang2=gcc"&gt;pure speed race&lt;/a&gt; with most languages. Most people think of speed and ease-of-use as polar opposites - probably because they remember the pain of writing C. &lt;a href="http://www.cython.org/"&gt;Cython&lt;/a&gt; tries to eliminate that duality and lets you …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I love python for its beautiful code and practicality. But it's not going to win a &lt;a href="http://shootout.alioth.debian.org/debian/benchmark.php?test=all&amp;amp;lang=python&amp;amp;lang2=gcc"&gt;pure speed race&lt;/a&gt; with most languages. Most people think of speed and ease-of-use as polar opposites - probably because they remember the pain of writing C. &lt;a href="http://www.cython.org/"&gt;Cython&lt;/a&gt; tries to eliminate that duality and lets you have python syntax with C data types and functions - the best of both worlds. Keeping in mind that I'm by no means an expert at this, here are my notes based on my first real experiment with Cython:&lt;/p&gt;
&lt;p&gt;EDIT: Based on some feedback I've received there seems to be some confusion - Cython is for generating &lt;em&gt;C extensions to Python&lt;/em&gt; not standalone programs. The whole point is to speed up an existing python app one function at a time. No rewriting the whole application in C or Lisp. No &lt;a href="http://www.dalkescientific.com/writings/NBN/c_extensions.html"&gt;writing C extensions by hand&lt;/a&gt;. Just an easy way to get C speed and C data types into your slow python functions. &lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;So lets say we want to make this function faster. It is the &lt;a href="http://mathworld.wolfram.com/GreatCircle.html"&gt;"great circle" calculation&lt;/a&gt;, a quick spherical trig problem to calculate distance along the earth's surface between two points:&lt;/p&gt;
&lt;p&gt;&lt;em&gt;p1.py&lt;/em&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;math&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;great_circle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lon1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;lat1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;lon2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;lat2&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;radius&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3956&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;#miles&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;math&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pi&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;180.0&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;90.0&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;lat1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;90.0&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;lat2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;theta&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lon2&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;lon1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;math&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;acos&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;math&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;math&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;
&lt;span class="w"&gt;                  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;math&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;math&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;math&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;theta&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;radius&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Lets try it out and &lt;a href="http://www.diveintopython.net/performance_tuning/timeit.html"&gt;time it&lt;/a&gt; over 1/2 million function calls:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;timeit&lt;/span&gt;

&lt;span class="n"&gt;lon1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;lat1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;lon2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;lat2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;72.345&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;34.323&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;61.823&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;54.826&lt;/span&gt;
&lt;span class="n"&gt;num&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;500000&lt;/span&gt;

&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;timeit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Timer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;p1.great_circle(&lt;/span&gt;&lt;span class="si"&gt;%f&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="si"&gt;%f&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="si"&gt;%f&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="si"&gt;%f&lt;/span&gt;&lt;span class="s2"&gt;)&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lon1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;lat1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;lon2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;lat2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="w"&gt;                       &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;import p1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Pure python function&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timeit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;num&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;sec&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;About &lt;strong&gt;2.2 seconds&lt;/strong&gt;. Too slow! &lt;/p&gt;
&lt;p&gt;Lets try a quick rewrite in Cython and see if that makes a difference:
&lt;em&gt;c1.pyx&lt;/em&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;math&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;great_circle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;lon1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;lat1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;lon2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;lat2&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;cdef&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;radius&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;3956.0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;cdef&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pi&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;3.14159265&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;cdef&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pi&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;180.0&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;cdef&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;theta&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;90.0&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;lat1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;90.0&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;lat2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;theta&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lon2&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;lon1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;math&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;acos&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;math&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;math&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;math&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;math&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;math&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;theta&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;radius&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Notice that we still &lt;em&gt;import math&lt;/em&gt; - cython lets you mix and match python and C data types to some extent. The conversion is handled automatically though not without cost. In this example all we've done is define a &lt;em&gt;python&lt;/em&gt; function, declare its input parameters to be floats, and declare a static C float data type for all the variables. It still uses the python math module to do the calcs. &lt;/p&gt;
&lt;p&gt;Now we need to convert this to C code and compile the python extension. The best way to do this is through a &lt;a href="http://ldots.org/pyrex-guide/2-compiling.html#distutils"&gt;setup.py distutils script&lt;/a&gt;. But we'll do it the &lt;a href="http://ldots.org/pyrex-guide/2-compiling.html#gcc"&gt;manual way&lt;/a&gt; for now to see what's happening:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;this&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;will&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;create&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;C&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;source&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;build&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;python&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;extension&lt;/span&gt;
&lt;span class="nx"&gt;cython&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pyx&lt;/span&gt;

&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Compile&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;
&lt;span class="nx"&gt;gcc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;fPIC&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;I&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;usr&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;include&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;python2&lt;/span&gt;&lt;span class="m m-Double"&gt;.5&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;

&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Link&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;into&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;shared&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;library&lt;/span&gt;
&lt;span class="nx"&gt;gcc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;shared&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;o&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;o&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;so&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Now you should have a c1.so (or .dll) file which can be imported in python. Lets give it a run:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;timeit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Timer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;c1.great_circle(&lt;/span&gt;&lt;span class="si"&gt;%f&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="si"&gt;%f&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="si"&gt;%f&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="si"&gt;%f&lt;/span&gt;&lt;span class="s2"&gt;)&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lon1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;lat1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;lon2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;lat2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="w"&gt;                     &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;import c1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Cython function (still using python math)&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timeit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;num&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;sec&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;About &lt;strong&gt;1.8 seconds&lt;/strong&gt;. Not the kind of speedup we were hoping for but its a start. The bottleneck must be in the usage of the python math module. Lets use the C standard library trig functions instead:&lt;/p&gt;
&lt;p&gt;&lt;em&gt;c2.pyx&lt;/em&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;cdef extern from &amp;quot;math.h&amp;quot;:
    float cosf(float theta)
    float sinf(float theta)
    float acosf(float theta)

def great_circle(float lon1,float lat1,float lon2,float lat2):
    cdef float radius = 3956.0 
    cdef float pi = 3.14159265
    cdef float x = pi/180.0
    cdef float a,b,theta,c

    a = (90.0-lat1)*(x)
    b = (90.0-lat2)*(x)
    theta = (lon2-lon1)*(x)
    c = acosf((cosf(a)*cosf(b)) + (sinf(a)*sinf(b)*cosf(theta)))
    return radius*c
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Instead of importing the math module, we use &lt;em&gt;cdef extern&lt;/em&gt; which uses the C function declarations from the specified include header (in this case  math.h from the C standard library). We've replaced the calls to some of the expensive python functions and are ready to build the new shared library and re-test:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;timeit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Timer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;c2.great_circle(&lt;/span&gt;&lt;span class="si"&gt;%f&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="si"&gt;%f&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="si"&gt;%f&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="si"&gt;%f&lt;/span&gt;&lt;span class="s2"&gt;)&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lon1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;lat1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;lon2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;lat2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="w"&gt;                     &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;import c2&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Cython function (using trig function from math.h)&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timeit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;num&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;sec&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Now that's a bit more like it. ** 0.4 seconds ** - a 5x speed increase over the pure python function. What else can we do to speed things up? Well c2.great_circle() is still a python function which means that calling it incurs the overhead of the python API, constructing the argument tuple, etc. If we could write it as a pure C function, we might be able to speed things up a bit. &lt;/p&gt;
&lt;p&gt;&lt;em&gt;c3.pyx&lt;/em&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;cdef extern from &amp;quot;math.h&amp;quot;:
    float cosf(float theta)
    float sinf(float theta)
    float acosf(float theta)

cdef float _great_circle(float lon1,float lat1,float lon2,float lat2):
    cdef float radius = 3956.0 
    cdef float pi = 3.14159265
    cdef float x = pi/180.0
    cdef float a,b,theta,c

    a = (90.0-lat1)*(x)
    b = (90.0-lat2)*(x)
    theta = (lon2-lon1)*(x)
    c = acosf((cosf(a)*cosf(b)) + (sinf(a)*sinf(b)*cosf(theta)))
    return radius*c

def great_circle(float lon1,float lat1,float lon2,float lat2,int num):
    cdef int i
    cdef float x
    for i from 0 &amp;lt; = i &amp;lt; num:
        x = _great_circle(lon1,lat1,lon2,lat2)
    return x
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Notice that we still have a python function wrapper (&lt;em&gt;def&lt;/em&gt;) which takes an extra argument, num. The looping is done inside this function with &lt;code&gt;for i from 0 &amp;lt; = i &amp;lt; num:&lt;/code&gt; instead of the more pythonic but slower &lt;code&gt;for i in range(num):&lt;/code&gt;. The actual work is done in a C function (&lt;em&gt;cdef&lt;/em&gt;) which returns float type. This runs in &lt;strong&gt;0.2 seconds&lt;/strong&gt; - a 10x speed boost over the original python function. &lt;/p&gt;
&lt;p&gt;Just to confirm that we're doing things optimally, lets write a little app in pure C and time it:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="cp"&gt;#include&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cpf"&gt;&amp;lt;math .h&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;#include&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cpf"&gt;&amp;lt;stdio .h&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;#define NUM 500000&lt;/span&gt;

&lt;span class="kt"&gt;float&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;great_circle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;float&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;lon1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;float&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;lat1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;float&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;lon2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;float&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;lat2&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kt"&gt;float&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;radius&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;3956.0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kt"&gt;float&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pi&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;3.14159265&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kt"&gt;float&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pi&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;180.0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kt"&gt;float&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;theta&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;90.0&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;lat1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;90.0&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;lat2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;theta&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lon2&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;lon1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;acos&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;cos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;cos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;sin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;cos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;theta&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;radius&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kt"&gt;float&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;NUM&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;great_circle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;-72.345&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;34.323&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;-61.823&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;54.826&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;%f&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Now compile it with &lt;code&gt;gcc -lm -o ctest ctest.c&lt;/code&gt; and test it with &lt;code&gt;time ./ctest&lt;/code&gt;... about &lt;strong&gt;0.2 seconds as well&lt;/strong&gt;. This gives me confidence that my Cython extension is at least as efficient as my C code (which probably isn't saying much as my C skills are weak).&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Some cases will be more or less optimal for cython depending on how much looping, number-crunching and python-function-calling are slowing you down. In some cases people have reported 100 to 1000x speed boosts. For other tasks it might not be so helpful. Before going crazy rewriting your python code in Cython, keep this in mind:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;"We should forget about small efficiencies, say about 97% of the time: premature optimization is the root of all evil."  -- Donald Knuth&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;In other words, write your program in python first and see if it works alright. Most of the time it will... some times it will bog down. Use a &lt;a href="http://docs.python.org/lib/module-hotshot.html"&gt;profiler&lt;/a&gt; to find the slow functions and re-implement them in cython and you should see a quick return on investment.&lt;/p&gt;
&lt;p&gt;Links:
&lt;a href="http://trac.gispython.org/projects/PCL/wiki/WorldMill"&gt;WorldMill&lt;/a&gt; - a python module by Sean Gillies which uses Cython to provide a fast, clean python interface to the libgdal library for handling vector geospatial data.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://www.sagemath.org:9001/WritingFastPyrexCode"&gt;Writing Fast Pyrex code&lt;/a&gt; (Pyrex is the predecessor of Cython with similar goals and syntax)&lt;/p&gt;</content><category term="articles"></category><category term="c"></category><category term="cython"></category><category term="programming"></category><category term="python"></category></entry><entry><title>Spatial data in SQLite</title><link href="https://www.perrygeo.com/spatial-data-in-sqlite.html" rel="alternate"></link><published>2008-04-15T00:00:00-06:00</published><updated>2008-04-15T00:00:00-06:00</updated><author><name>Matthew T. Perry</name></author><id>tag:www.perrygeo.com,2008-04-15:/spatial-data-in-sqlite.html</id><summary type="html">&lt;p&gt;Slashgeo pointed me to a very interesting set of projects - &lt;a href="http://www.gaia-gis.it/spatialite/"&gt;SpatiaLite and VirtualShape&lt;/a&gt;. They provide a spatial data engine for the &lt;a href="http://www.sqlite.org/index.html"&gt;sqlite&lt;/a&gt; database. Think of it as the PostGIS of SQLite. It looks like this extends sqlite's spatial capabilities far beyond the &lt;a href="http://www.gdal.org/ogr/drv_sqlite.html"&gt;sqlite OGR driver&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;SpatiaLite provides many of the …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Slashgeo pointed me to a very interesting set of projects - &lt;a href="http://www.gaia-gis.it/spatialite/"&gt;SpatiaLite and VirtualShape&lt;/a&gt;. They provide a spatial data engine for the &lt;a href="http://www.sqlite.org/index.html"&gt;sqlite&lt;/a&gt; database. Think of it as the PostGIS of SQLite. It looks like this extends sqlite's spatial capabilities far beyond the &lt;a href="http://www.gdal.org/ogr/drv_sqlite.html"&gt;sqlite OGR driver&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;SpatiaLite provides many of the basic OGC Simple Features functions - transforming geometries between projections, spatial operations of bounding boxes, and some basic functions to disect, analyze and export geometries. &lt;/p&gt;
&lt;p&gt;VirtualShape provides the really neat ability to access a shapefile using the SpatiaLite/SQlite interface without having to import a copy - it reads directly off the shapefile by exposing the shapefile and its attributes as a "virtual table". I can think of a million uses for this. For example, lets say you have a shapefile of US counties and the number of voter in the 2004 election as an attribute in the dbf. You want to find the total voter count in each state:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ls&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;counties&lt;/span&gt;&lt;span class="o"&gt;.*&lt;/span&gt;
&lt;span class="n"&gt;counties&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dbf&lt;/span&gt;
&lt;span class="n"&gt;counties&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;prj&lt;/span&gt;
&lt;span class="n"&gt;counties&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shp&lt;/span&gt;
&lt;span class="n"&gt;counties&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shx&lt;/span&gt;
&lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sqlite3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;test&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;
&lt;span class="n"&gt;sqlite&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;load&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;SpatiaLite.so&amp;#39;&lt;/span&gt;
&lt;span class="n"&gt;sqlite&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;load&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;VirtualShape.so&amp;#39;&lt;/span&gt;
&lt;span class="n"&gt;sqlite&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;CREATE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;virtual&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;table&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;virtual_counties&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;using&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;VirtualShape&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;counties&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;sqlite&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;select&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;voters&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;total_voters&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;state_name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;virtual_counties&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;group&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;by&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;state_name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;by&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;total_voters&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;desc&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="mf"&gt;9830550.0&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;California&lt;/span&gt;
&lt;span class="mf"&gt;7563055.0&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;Florida&lt;/span&gt;
&lt;span class="mf"&gt;7346779.0&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;Texas&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Now this is fairly straightforward non-spatial SQL but the ability to run it against a shapfile without having to export to an intermediate data format is a very valuable tool. &lt;/p&gt;
&lt;p&gt;Links: 
* &lt;a href="http://www.sqlite.org/whentouse.html"&gt;When to use SQlite.&lt;/a&gt;
* A &lt;a href="http://video.google.com/videoplay?docid=-5160435487953918649"&gt;video presentation&lt;/a&gt; by Richard Hipp (the author of sqlite).&lt;/p&gt;</content><category term="articles"></category><category term="sqlite"></category></entry><entry><title>Shell history - Why not?</title><link href="https://www.perrygeo.com/shell-history-why-not.html" rel="alternate"></link><published>2008-04-11T00:00:00-06:00</published><updated>2008-04-11T00:00:00-06:00</updated><author><name>Matthew T. Perry</name></author><id>tag:www.perrygeo.com,2008-04-11:/shell-history-why-not.html</id><summary type="html">&lt;p&gt;What an odd meme .. I don't know why but I expected some more interesting results. I guess the majority of the commands I use are pretty pedestrian.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;history&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;awk&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;{a[$2]++ } END{for(i in a){print a[i] &amp;quot; &amp;quot; i}}&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;sort&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;rn&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;head&lt;/span&gt;
&lt;span class="mi"&gt;163&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;vi&lt;/span&gt;
&lt;span class="mi"&gt;48&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;screen&lt;/span&gt;
&lt;span class="mi"&gt;29&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;python&lt;/span&gt;
&lt;span class="mi"&gt;28 …&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</summary><content type="html">&lt;p&gt;What an odd meme .. I don't know why but I expected some more interesting results. I guess the majority of the commands I use are pretty pedestrian.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;history&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;awk&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;{a[$2]++ } END{for(i in a){print a[i] &amp;quot; &amp;quot; i}}&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;sort&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;rn&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;head&lt;/span&gt;
&lt;span class="mi"&gt;163&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;vi&lt;/span&gt;
&lt;span class="mi"&gt;48&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;screen&lt;/span&gt;
&lt;span class="mi"&gt;29&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;python&lt;/span&gt;
&lt;span class="mi"&gt;28&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ls&lt;/span&gt;
&lt;span class="mi"&gt;17&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cp&lt;/span&gt;
&lt;span class="mi"&gt;17&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cd&lt;/span&gt;
&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sqlite3&lt;/span&gt;
&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rm&lt;/span&gt;
&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sudo&lt;/span&gt;
&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;htop&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</content><category term="articles"></category><category term="unix"></category></entry><entry><title>Working hard for some REST</title><link href="https://www.perrygeo.com/working-hard-for-some-rest.html" rel="alternate"></link><published>2008-04-02T00:00:00-06:00</published><updated>2008-04-02T00:00:00-06:00</updated><author><name>Matthew T. Perry</name></author><id>tag:www.perrygeo.com,2008-04-02:/working-hard-for-some-rest.html</id><summary type="html">&lt;p&gt;I don't spend much time with web programming these days but I decided to give &lt;a href="http://webpy.org/"&gt;web.py&lt;/a&gt; (the minimalist python web framework) a shot and, while I was at it, try implementing a simple REST api.&lt;/p&gt;
&lt;p&gt;First of all, web.py is truly everything it claims to be - small, light …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I don't spend much time with web programming these days but I decided to give &lt;a href="http://webpy.org/"&gt;web.py&lt;/a&gt; (the minimalist python web framework) a shot and, while I was at it, try implementing a simple REST api.&lt;/p&gt;
&lt;p&gt;First of all, web.py is truly everything it claims to be - small, light and easy to deploy behind &lt;a href="http://www.lighttpd.net/"&gt;lighttpd&lt;/a&gt;. It gives you a ton of flexibility to implement anything however you want - which is a plus or minus depending on how you look at it. I liked the inifinte flexibility but I can see alot of refactoring taking place and features needing to be implemented just to match the functionality built into a more structured framework like Django.&lt;/p&gt;
&lt;p&gt;Back to the REST side of things. So I created a url-mapping to my "resources" or "nouns" and used the HTTP verbs (POST,GET,PUT,DELETE) to supply the interface. This was a joy to do in web.py which made it easy.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;urls = (&amp;quot;/thing/(\d+)&amp;quot;, &amp;quot;thing&amp;quot;)
...
class thing:
    def GET(self, thingid):
        # select query and render to template
        ....
    def POST(self, thingid):
        # insert query and redirect to /thing/thingid
        ....
    def DELETE(self, thingid):
        # delete query
        ....
    def PUT(self, thingid):
        # use cgi args to run update query on specified thing
        ....
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The hard part came when I realized that HTML forms do not implement DELETE or PUT methods! 2 of the 4 cornerstone HTTP verbs are not implemented in HTML forms? &lt;/p&gt;
&lt;p&gt;Surely this can be accomplished with a top-notch AJAX library. I tried Prototype.js and it appears that the PUT and DELETE methods are simply tunneled over POST with an extra arg attached and the server side has to handle it accordingly. So I ended up just using a straight XMLHttpRequest which works but has it's own problems.&lt;/p&gt;
&lt;p&gt;How are you supposed to call PUT or DELETE through a web page? Is XMLHttpRequest the only way? What about browsers without javascript?&lt;/p&gt;</content><category term="articles"></category><category term="ajax"></category><category term="django"></category><category term="rest"></category><category term="web.py"></category><category term="python"></category></entry><entry><title>Upcoming books</title><link href="https://www.perrygeo.com/upcoming-books.html" rel="alternate"></link><published>2008-03-12T00:00:00-06:00</published><updated>2008-03-12T00:00:00-06:00</updated><author><name>Matthew T. Perry</name></author><id>tag:www.perrygeo.com,2008-03-12:/upcoming-books.html</id><summary type="html">&lt;p&gt;There are two new books coming out this summer which fill a valuable niche in the open-source GIS bookshelf:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="http://www.springer.com/statistics/statistical+theory+and+methods/book/978-0-387-78170-9"&gt;Applied Spatial Data Analysis with R&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="http://www.pragprog.com/titles/gsdgis"&gt;Desktop GIS: Mapping the Planet with Open Source&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These are both written by some of the top developers within their respective topics and I'm really …&lt;/p&gt;</summary><content type="html">&lt;p&gt;There are two new books coming out this summer which fill a valuable niche in the open-source GIS bookshelf:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="http://www.springer.com/statistics/statistical+theory+and+methods/book/978-0-387-78170-9"&gt;Applied Spatial Data Analysis with R&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="http://www.pragprog.com/titles/gsdgis"&gt;Desktop GIS: Mapping the Planet with Open Source&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These are both written by some of the top developers within their respective topics and I'm really looking forward to reading them. &lt;/p&gt;</content><category term="articles"></category></entry><entry><title>Google Earth and the tilt sensor joystick on the X61s</title><link href="https://www.perrygeo.com/google-earth-and-the-tilt-sensor-joystick-on-the-x61s.html" rel="alternate"></link><published>2008-02-17T00:00:00-07:00</published><updated>2008-02-17T00:00:00-07:00</updated><author><name>Matthew T. Perry</name></author><id>tag:www.perrygeo.com,2008-02-17:/google-earth-and-the-tilt-sensor-joystick-on-the-x61s.html</id><summary type="html">&lt;p&gt;The X61s is one bad-ass machine. Besides the great performance, battery life and solid engineering, there are other hidden gems. Like the tilt sensors that were designed to protect the hard drive in case of a drop can also be used to detect the laptops motion under more normal circumstances …&lt;/p&gt;</summary><content type="html">&lt;p&gt;The X61s is one bad-ass machine. Besides the great performance, battery life and solid engineering, there are other hidden gems. Like the tilt sensors that were designed to protect the hard drive in case of a drop can also be used to detect the laptops motion under more normal circumstances. &lt;/p&gt;
&lt;p&gt;There are &lt;a href="http://www-128.ibm.com/developerworks/linux/library/l-knockage.html"&gt;some&lt;/a&gt; &lt;a href="http://www.pberndt.com/Programme/Linux/pyhdaps/index.html#"&gt;interesting&lt;/a&gt; &lt;a href="http://blog.micampe.it/articles/2006/06/04/here-comes-the-smackpad"&gt;applications&lt;/a&gt; that use some simple statistics to determine when the machine is "tapped" or julted to left or right. You can then assign actions to unique combinations of taps.&lt;/p&gt;
&lt;p&gt;These applications all use the sysfs interface to the sensors (_ cat /sys/bus/platform/devices/hdaps/position _ will show your position in the x and y axis). But the sensors also provide a joystick interface that allow you to tilt the laptop along the two horizontal axes to control any number of applications. Including Google Earth.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Install &lt;a href="http://www.thinkwiki.org/wiki/Tp_smapi"&gt;tp_smapi&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Test the sensors by running hdaps-gl , a simple OpenGL app showing the real-time tilt of your thinkpad.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Run jscal to calibrate the joystick. You'll need to install the "joystick" package for this. The command is:
&lt;code&gt;jscal -c /dev/input/js0&lt;/code&gt;
After which you should keep your laptop level for a few seconds. Then, when prompted, tilt left, center, right, back (towards you), center, then forward.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Now fire up Google Earth. Open the Options menu, go to Navigation and select Enable Contoller. &lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img alt="" src="/assets/img/GE_joystick.jpg"&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;You should now be able to zoom around by tilting the laptop.  The keyboard shortcuts really help when you're in this mode (Ctl-Up/Down to zoom, Shift-Up/Down to tilt, Shift-Left/Right to pivot). &lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;There's also a neat &lt;a href="http://www.metafilter.com/52312/More-accellerometer-goodness"&gt;Perl-script technique to control a web-based google map&lt;/a&gt; which has some cool potential for an openlayers based system. &lt;/p&gt;
&lt;p&gt;Since most Apple laptops have a similar sensor, you should be able to get the same thing going on your Macbook.  Try it out..its alot more fun that using the mouse!&lt;/p&gt;</content><category term="articles"></category></entry><entry><title>The shiny new X61s</title><link href="https://www.perrygeo.com/the-shiny-new-x61s.html" rel="alternate"></link><published>2008-02-16T00:00:00-07:00</published><updated>2008-02-16T00:00:00-07:00</updated><author><name>Matthew T. Perry</name></author><id>tag:www.perrygeo.com,2008-02-16:/the-shiny-new-x61s.html</id><summary type="html">&lt;p&gt;My HP laptop was nearing 5 years old. It had held up extremely well but most modern software taxed it to the absolute limits (just having firefox open with a flash ad in one tab was enough to send the system load through the roof). So I decided to try …&lt;/p&gt;</summary><content type="html">&lt;p&gt;My HP laptop was nearing 5 years old. It had held up extremely well but most modern software taxed it to the absolute limits (just having firefox open with a flash ad in one tab was enough to send the system load through the roof). So I decided to try something new.&lt;/p&gt;
&lt;p&gt;I was looking for something in the ultra-portable range. I tried out the OLPC and looked seriously at the Asus eeepc for a while. But they were far too difficult for me to type on. Ergonomics were extremely important and the only ultraportable that consistently rated high in that department was the IBM/Lenovo thinkpads. The X61s was appealing with its low voltage core2 duo and 2GB of RAM. All that in a small package about 3 lbs and about an inch thick.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="/assets/img/x61s.jpg"&gt;&lt;/p&gt;
&lt;p&gt;So the X61s arrived and I figured I'd give it a try with the "stock" software. It was my first experience with Vista and I gave it my best shot. After about 1/2 hour of excessive clicking, boggy performance and pop-up windows, I shrunk the ntfs partition and installed Ubuntu Hardy Heron Alpha 4.  &lt;/p&gt;
&lt;p&gt;Sound, wireless with WPA, Compiz with 3D; the major things that normally plague a linux laptop install worked right out of the box. On the other hand, I'm running into a few bugs in nautilus (this is is alpha software after all), I can't get bluetooth working, suspending to ram works but is a little buggy (have to restart some services manually) and I had to edit a few config files and compile a kernel module to utilize all the bells and whistles provided by the hardware. But it is still more fun than using Vista.&lt;/p&gt;
&lt;p&gt;One thing that really shines on this machine is the battery. I got the 8-cell extended life battery and used some powertop tweaks cut my power consumption and was able to get the wattage down in the 10 to 15 watt range depending on usage patterns. No wonder it is energy star compliant! With that kind of wattage and battery capacity, I'm easily getting about 6 to 7 full hours of battery life.&lt;/p&gt;
&lt;p&gt;Some tips if you're setting up Linux on your X61s:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;First and foremost, read &lt;a href="http://thinkwiki.org"&gt;thinkwiki&lt;/a&gt;. There you'll find 95% of your answers. But to summarize my experience: &lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Upgrade your BIOS first (this is a good reason to keep your Vista partion around since Lenovo ships some handy update utils for windows). &lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Install the &lt;a href="http://www.thinkwiki.org/wiki/Tp_smapi"&gt;tp_smapi kernel module&lt;/a&gt; with HDAPS support. This will enable Linux to access the hard drive sensors for disk protection, motion sensing and the joystick interface&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The big blue "Thinkvantage" button doesn't work out of the box. I'm not sure what it &lt;em&gt;should&lt;/em&gt; do but its a nicely placed button so &lt;a href="http://www.thinkwiki.org/wiki/How_to_get_special_keys_to_work#acpi_fakekey"&gt;don't let it go to waste&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Tweak the power consumption. For the impatient, just install powertop and follow the instructions .. it will tell you what processes are waking your CPU and how to stop them. Also check out &lt;a href="http://lesswatts.org"&gt;Less Watts&lt;/a&gt; - a full resource for tweaking linux power consumption.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Configure your &lt;a href="http://www.thinkwiki.org/wiki/How_to_configure_the_TrackPoint"&gt;trackpoint&lt;/a&gt; pointer and buttons. This involves setting up you xorg.conf file to emulate a middle scroll wheel as well as tweaking the speed and sensitivity of the pointer. BTW - if you've never tried a pointer, give it a shot ... I've found it much more comfortable than a touchpad.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="http://samwel.tk/laptop_mode/"&gt;Laptop-mode&lt;/a&gt; , a set of kernel and userspace tools to manage hard-drive power consumption, can be handy. It can also be &lt;a href="https://bugs.launchpad.net/mandriva/+source/laptop-mode-tools/+bug/59695"&gt;deadly to your disk if configured incorrectly&lt;/a&gt;. Basically it aggressively spins down the disk after short periods of inactivity to save power. Inevitably an application will try to hit the disk again and it will spin right back up. This leads to an unreasonably high amount of load cycles (100 per hour) and the drive can only handle a finite amount before failure (~600,000).  You can configure it for more sane behavior but do your research before you enable laptop-mode! And check out smartctl to monitor the disks health. &lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If, after you unsuspend the machine, your screen is way too dark, try Ctl-Alt-F1 followed by Ctl-Alt-F7. There are some other hacks involving acpi configuration or grub kernel options but none of them have worked for me yet.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;</content><category term="articles"></category></entry><entry><title>Human Impacts on the Global Marine Ecosystem</title><link href="https://www.perrygeo.com/human-impacts-on-the-global-marine-ecosystem.html" rel="alternate"></link><published>2008-02-15T00:00:00-07:00</published><updated>2008-02-15T00:00:00-07:00</updated><author><name>Matthew T. Perry</name></author><id>tag:www.perrygeo.com,2008-02-15:/human-impacts-on-the-global-marine-ecosystem.html</id><summary type="html">&lt;p&gt;&lt;a href="http://sciencenow.sciencemag.org/cgi/content/full/2008/214/2"&gt;We did it&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;As some of you may know in 2005 through 2006,  I was part of a research team[1] , led by Ben Halpern at NCEAS, developing a global model of human impacts on the marine ecosystem. We created or compiled 17 high-resolution global datasets of human-induced threats (land-based …&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;a href="http://sciencenow.sciencemag.org/cgi/content/full/2008/214/2"&gt;We did it&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;As some of you may know in 2005 through 2006,  I was part of a research team[1] , led by Ben Halpern at NCEAS, developing a global model of human impacts on the marine ecosystem. We created or compiled 17 high-resolution global datasets of human-induced threats (land-based pollutants, fishing, shipping, climate change, etc.) and 20 ocean habitat datasets. These were combined to create an impact index which models the cumulative level of human-induced stress on our oceans. &lt;/p&gt;
&lt;p&gt;&lt;a href="http://ebm.nceas.ucsb.edu/GlobalMarine/models/model/jpg/model_high_res.jpg"&gt;&lt;img alt="" src="/assets/img/map_400.jpg"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The results were published today in &lt;a href="http://www.sciencemag.org/cgi/content/abstract/319/5865/948"&gt;Science&lt;/a&gt; magazine and presented yesterday at the &lt;a href="http://news.aaas.org/releases/2008_ann_mtg/scientists-track-human-footpri.html"&gt;AAAS Annual Meeting&lt;/a&gt;. To summarize, we found that the entire ocean is affected and 40% is heavily impacted. It is not all bad news as there are many areas of relatively low impact which could provide examples for ecosystem restoration and opportunities for conservation. The global map is the first of its kind and will help clarify and quantify our cumulative impacts on the ocean and allow us to focus efforts geographically. The model is not perfect and can't really be used to make decisions at a very localized scale but, given the available globally-consistent, reasonably-high-resolution data for all the various ocean threats and habitats, this is the best effort to date. The model itself is relatively simple with a very clear methodology which will allow scientists to tweak the parameters and add better data as it becomes available. For those of you interested in the GIS modeling end, NCEAS has a &lt;a href="http://www.nceas.ucsb.edu/GlobalMarine"&gt;great summary&lt;/a&gt; of the data used in the model. Most of the data are available as raster data products or KML.&lt;/p&gt;
&lt;p&gt;The media has picked up on the story with &lt;a href="http://www.npr.org/templates/story/story.php?storyId=19059595"&gt;NPR&lt;/a&gt;, &lt;a href="http://www.msnbc.msn.com/id/23155918/"&gt;MSNBC&lt;/a&gt;, &lt;a href="http://www.washingtonpost.com/wp-dyn/content/article/2008/02/14/AR2008021401992.html?hpid=topnews"&gt;The Washington Post&lt;/a&gt;, &lt;a href="http://www.usatoday.com/tech/science/environment/2008-02-14-oceans-human-activity_N.htm"&gt;USA Today&lt;/a&gt; and &lt;a href="http://news.nationalgeographic.com/news/2008/02/080214-oceans.html"&gt;National Geographic&lt;/a&gt; covering it (to name a few). I especially recommend the NPR site as it has a great animation and an audio segment. &lt;/p&gt;
&lt;p&gt;So congratulations to everyone who made this happen! &lt;/p&gt;
&lt;p&gt;&lt;em&gt;[1] Benjamin S. Halpern, Shaun Walbridge, Kimberly A. Selkoe, Carrie V. Kappel, Fiorenza Micheli, Caterina D'Agrosa, John F. Bruno, Kenneth S. Casey, Colin Ebert, Helen E. Fox, Rod Fujita, Dennis Heinemann, Hunter S. Lenihan, Elizabeth M.P. Madin, Matthew T. Perry, Elizabeth R. Selig, Mark Spalding, Robert Steneck, Reg Watson (2008). A global map of human impact on marine ecosystems. Science, vol. 319&lt;/em&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;EDIT:&lt;/p&gt;
&lt;p&gt;Some additional articles:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="http://www.nytimes.com/interactive/2008/02/25/science/earth/20080225_COAST_GRAPHIC.html"&gt;New York Times&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="http://youtube.com/watch?v=0qh49Da5A5M"&gt;BBC Video&lt;/a&gt; on YouTube &lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;</content><category term="articles"></category></entry><entry><title>Why is the command line a dying art?</title><link href="https://www.perrygeo.com/why-is-the-command-line-a-dying-art.html" rel="alternate"></link><published>2008-02-02T00:00:00-07:00</published><updated>2008-02-02T00:00:00-07:00</updated><author><name>Matthew T. Perry</name></author><id>tag:www.perrygeo.com,2008-02-02:/why-is-the-command-line-a-dying-art.html</id><summary type="html">&lt;p&gt;Sadly, a lot of GIS folks have never come into contact with a command line interface (CLI) . I've met even experienced computer users who, when faced with a command-line prompt, experience some autonomous nervous system lock up that causes their eyes to glaze over and prevents any knowledge from entering …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Sadly, a lot of GIS folks have never come into contact with a command line interface (CLI) . I've met even experienced computer users who, when faced with a command-line prompt, experience some autonomous nervous system lock up that causes their eyes to glaze over and prevents any knowledge from entering their brain from that moment forward. The all-Windows, all-GUI mentality of the current GIS market leaders just doesn't expose you to it (if you remember working with coverages at the ESRI Arc/Info command line, you official qualify as an "old-timer"). And the DOS command line is virtually invisible to XP and vista users. Linux users are more CLI aware but this is even becoming less important as distros such as ubuntu GUI-ify everything.&lt;/p&gt;
&lt;p&gt;So why the fear of the command line? Why is it assumed to be more "complicated" than a graphical user interface (GUI)? I have found that, in some cases, the opposite is true ... there is something reassuringly simple about typing something and getting a response back. It feels like you are in direct control of the computer. Which, indeed, you &lt;em&gt;always&lt;/em&gt; are. The computers always do exactly what you tell them, whether you are in a GUI or a CLI. But GUIs attempt to abstract away the details so that you &lt;em&gt;don't need to know&lt;/em&gt; exactly what you're telling the computer to do. This nice fluffy feeling comes at the cost of many important factors. &lt;/p&gt;
&lt;h2&gt;The benefits of the command line interface&lt;/h2&gt;
&lt;h3&gt;Automation&lt;/h3&gt;
&lt;p&gt;If you had, for instance, monitoring data coming in in a hourly basis and needed to process the data, would you want to be on call 24 hours a day to click a few buttons. Of course not. Write a command that performs the job and schedule it to execute at some regular interval. (I wonder if those guys on LOST ever thought to just set up a cron job to enter the numbers in the hatch?)  &lt;/p&gt;
&lt;h3&gt;Repeatability&lt;/h3&gt;
&lt;p&gt;Whenever I show someone a CLI-based method for solving their problem, they almost immediately say (or at least imply) that the typing is too much trouble.  Consider this command to convert a .tif image to ERDAS .img (HFA) format:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;cd /data/images
gdal_translate -of HFA aerial.tif aerial.tif.img
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;You might ask, "Why not just use a GUI, click a button or two, and get your output".  Sure. Now do that for 2,000 tif images. With a CLI you only have to type a few extra lines. &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;cd /data/images
for i in *.tif; do 
  gdal_translate -of HFA $i $i.img;
done
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;Documentability&lt;/h3&gt;
&lt;p&gt;There is nothing more important to a GIS Analyst than documenting his/her work! We live by metadata and methods write-ups.   Now picture an intense 5 hour work session ... everything needed to get out by 2pm. You're done and now it's time to document your procedure and methods. With the CLI, you copy and paste your commands from the terminal or simply look at your command history which will show &lt;em&gt;exactly&lt;/em&gt; what you did and how. You can store this in a text file and come back to it months later and be able to re-run the procedure. &lt;/p&gt;
&lt;p&gt;With the GUI, you have to remember and describe every click, every sub-menu, every option, every action taken to arrive at the answer. Often this requires verbose description, screenshots, etc. None of which is recorded in any history file of course. And of course, when the client inevitably comes back the next day with modifications, none of it is repeatable in any automated fashion with a GUI. &lt;/p&gt;
&lt;h3&gt;Accessibility&lt;/h3&gt;
&lt;p&gt;It's just plain text with a CLI. You can print it out and study it on the bus. You can email the whole process to co-workers. You can use a concurrent versioning system to keep track of changes to scripted procedures. You can transfer massive amounts of knowledge without having to sit down and go through everything step-by-step, click-by-click in a visual interface. &lt;/p&gt;
&lt;h3&gt;Accuracy&lt;/h3&gt;
&lt;p&gt;Far too often, GUI designers make over-reaching assumptions about how things should work. The idea is often that the user should not need to know anything more than the absolute minumum.  To use a car analogy, the driver turns the key, presses the pedal and steers but does not need to know what goes on under the hood.  This works most of the time. But the &lt;a href="http://en.wikipedia.org/wiki/Leaky_abstraction"&gt;law of leaky abstractions&lt;/a&gt; usually takes hold and something inevitably breaks or performs differently than expected.  Since the CLI does not hold your hand (it executes the exact command you give it) it more accurately mimics the actual physical interaction with the computer and is much more useful in debugging and investigating complex problems. &lt;/p&gt;
&lt;p&gt;So basically, don't make the mistake of thinking that a pretty window will always contain the magic button to get the job done. In many cases, a command line is much more efficient, even essential. If you don't know how to effectively work in a command-line environment, do yourself a huge favor and learn.&lt;/p&gt;
&lt;p&gt;Oh and I'd be remiss if I didn't mention &lt;a href="http://www.amazon.com/Beginning-was-Command-Line-Neal-Stephenson/dp/0380815931"&gt;Neal Stephenson's book &lt;/a&gt; on the subject ... a bit technically outdated but a great quick read on why command lines are still very relevant in the face of increasingly sophisticated graphical interfaces.&lt;/p&gt;</content><category term="articles"></category></entry><entry><title>Impervious surface deliniation with GRASS</title><link href="https://www.perrygeo.com/impervious-surface-deliniation-with-grass.html" rel="alternate"></link><published>2008-01-26T00:00:00-07:00</published><updated>2008-01-26T00:00:00-07:00</updated><author><name>Matthew T. Perry</name></author><id>tag:www.perrygeo.com,2008-01-26:/impervious-surface-deliniation-with-grass.html</id><summary type="html">&lt;p&gt;Watersheds with lots of roads, buildings, parking lots, rock surfaces, compacted dirt, etc tend to prevent inflitration and cause rapid runoff in response to rainfall. This poses a &lt;a href="http://chesapeake.towson.edu/landscape/impervious/what_imp.asp"&gt;number of challenges for managing stormwater&lt;/a&gt; and water quality. Not surprisingly, the percentage of hydrologically impervious surface in a given watershed is …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Watersheds with lots of roads, buildings, parking lots, rock surfaces, compacted dirt, etc tend to prevent inflitration and cause rapid runoff in response to rainfall. This poses a &lt;a href="http://chesapeake.towson.edu/landscape/impervious/what_imp.asp"&gt;number of challenges for managing stormwater&lt;/a&gt; and water quality. Not surprisingly, the percentage of hydrologically impervious surface in a given watershed is an important factor in many hydrologic models. Using standard aerial photography and GRASS, it's a relatively simple process to create an impervious surface map using supervised classification.&lt;/p&gt;
&lt;p&gt;First find an aerial photo. I grabbed a NAIP image from &lt;a href="http://new.casil.ucdavis.edu/casil/remote_sensing/naip_2005/county_mosaics/"&gt;CASIL&lt;/a&gt; but you might want to try &lt;a href="http://crschmidt.net/blog/archives/285/producing-a-large-image-from-openaerialmap/"&gt;using OpenAerialMap&lt;/a&gt;. The red, green and blue visible bands are usually sufficient for differentiating between impervious and pervious land use types... For distinguishing different types of vegetation you might want to use a multispectral imagery source with non-visible bands (ie near infrared) but this is usually lower resolution (eg. 30 meter pixels of Landsat) or much more expensive.&lt;/p&gt;
&lt;p&gt;Next we jump into GRASS and import our image into a new location:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;r.in.gdal -e input=naip.img output=naip location=impervious
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Exit and log back into your new location. If you look at the imported rasters, you'll see three rasters, not one. Each band (R, G and B) gets imported separately.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;GRASS 6.3.cvs (impervious):~/&amp;gt;  g.list rast
raster files available in mapset permanent:
naip.1 naip.2 naip.3
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;We need to indicate that these rasters form a logical group&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;group&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;group&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;naip2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;subgroup&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;naip2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;input&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;naip&lt;/span&gt;&lt;span class="mf"&gt;.3&lt;/span&gt;&lt;span class="nv"&gt;@PERMANENT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;naip&lt;/span&gt;&lt;span class="mf"&gt;.2&lt;/span&gt;&lt;span class="nv"&gt;@PERMANENT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;naip&lt;/span&gt;&lt;span class="mf"&gt;.1&lt;/span&gt;&lt;span class="nv"&gt;@PERMANENT&lt;/span&gt;
&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;group&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;naip2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;At any time you can list the rasters in a given group/subgroup to confirm.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;i.group -l -g group=naip2 subgroup=naip2
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Now the real heart of the process. We need to define "training areas" which are polygons around representative land use types. I used QGIS to load the aerial photo and create a new polygon layer with an integer attribute field called vegnum. I digitized a few rocks, paved areas, rooftops and dirt roads to represent the impervious areas to which I assigned vegnum=1. Then I selected some grasslands, forests, lakes and chaparral  and assigned 2 as the vegnum. The next step is to load the polygon data into GRASS and rasterize it (&lt;em&gt;in retrospect it would have just been easier to create the grass vector layer from scratch in QGIS to avoid the import step&lt;/em&gt;). Note that the vegnum field is specified as the raster value column.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ogr&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;o&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;dsn&lt;/span&gt;&lt;span class="p"&gt;=.&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;training&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;train1_utm&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;train1_utm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;shp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;output&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;train1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;layer&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;train1_utm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;min_area&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="m m-Double"&gt;0.0001&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;boundary&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;snap&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rast&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;train1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;output&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;train1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;use&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;attr&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;column&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;vegnum&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;point&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;line&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;area&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;layer&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;4096&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Next we use i.gensig to generate a spectral signature (the statistical profile; mean and covariance matrix of the input pixels) for the training areas.  &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;i.gensig trainingmap=train1 group=naip2 subgroup=naip2 signaturefile=naip2_train1.sig
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Now that we have a signature of impervious vs. non-impervious surfaces, we can use the maximum likelihood method to classify each pixel into the highest probability category.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;i.maxlik group=naip2 subgroup=naip2 sigfile=naip2_train1.sig class=imperv
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;You might notice a slight speckled, noisy appearance due to things like shadows, reflections or imperfect training areas. Usually these small 1-pixel deviations are not interesting enough to keep so we can smooth out the image taking the mode (most comon) cell in a 3x3 window.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;r.neighbors input=imperv output=imperv_mode method=mode size=3
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;And here are the results... calculating imperviousness will most likely be an iterative process so be prepared to evaluate the output, tweak the training areas and rerun the process a few times. Once you're happy with the results, you can use zonal statistics with a tool like starspan to find the percent imperviousness of your watersheds or other regions.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="/assets/img/aerial.jpg"&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="/assets/img/imperv_smooth.png"&gt;&lt;/p&gt;</content><category term="articles"></category></entry><entry><title>A GUI for GDAL and GMT'</title><link href="https://www.perrygeo.com/a-gui-for-gdal-and-gmt.html" rel="alternate"></link><published>2008-01-06T00:00:00-07:00</published><updated>2008-01-06T00:00:00-07:00</updated><author><name>Matthew T. Perry</name></author><id>tag:www.perrygeo.com,2008-01-06:/a-gui-for-gdal-and-gmt.html</id><summary type="html">&lt;p&gt;In the why-haven't-I-ever-heard-of-this department:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href="http://w3.ualg.pt/%7Ejluis/mirone/manual.htm"&gt;Mirone&lt;/a&gt; is a Windows MATLAB-based framework tool that allows the display and manipulation of a large number of grid formats through its interface with the GDAL library. Its main purpose is to provide users with an easy-to-use graphical interface to the more commonly used programs of …&lt;/p&gt;&lt;/blockquote&gt;</summary><content type="html">&lt;p&gt;In the why-haven't-I-ever-heard-of-this department:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href="http://w3.ualg.pt/%7Ejluis/mirone/manual.htm"&gt;Mirone&lt;/a&gt; is a Windows MATLAB-based framework tool that allows the display and manipulation of a large number of grid formats through its interface with the GDAL library. Its main purpose is to provide users with an easy-to-use graphical interface to the more commonly used programs of the GMT package. &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;There is also a version that does not depend on MATLAB which is what I decided to try.  This is a great package; easy to install, very usable, lots of high-end raster functionality, and a good sense of humor...&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="/assets/img/mirone.png"&gt;&lt;/p&gt;
&lt;p&gt;Considering GMT and GDAL can be a bit challenging and unfamiliar for a typical windows user, Mirone is a huge step forward. &lt;/p&gt;
&lt;p&gt;Among some of the functionality that is an absolute pleasure to work with compared to some other software packages: surface profiles, image-flipping, DEM derivatives,  color-ramping, contouring, histograms, kernel filtering... And that's just scratching the surface. I highly recommend checking it out.&lt;/p&gt;</content><category term="articles"></category></entry><entry><title>More on Google Charts and a python interface</title><link href="https://www.perrygeo.com/more-on-google-charts-and-a-python-interface.html" rel="alternate"></link><published>2007-12-19T00:00:00-07:00</published><updated>2007-12-19T00:00:00-07:00</updated><author><name>Matthew T. Perry</name></author><id>tag:www.perrygeo.com,2007-12-19:/more-on-google-charts-and-a-python-interface.html</id><summary type="html">&lt;p&gt;Well it's been almost a full two weeks since &lt;a href="http://code.google.com/apis/chart/"&gt;google charts API&lt;/a&gt; came out.  A really nice service but it's only going to be useful with a high-level programming API. Enter &lt;a href="http://pygooglechart.slowchop.com/"&gt;PyGoogleChart&lt;/a&gt; .. a python interface to generate google chart urls. &lt;/p&gt;
&lt;p&gt;Taking one of my &lt;a href="http://www.perrygeo.net/wordpress/?p=64"&gt;previous example datasets&lt;/a&gt;, here's the 10-second …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Well it's been almost a full two weeks since &lt;a href="http://code.google.com/apis/chart/"&gt;google charts API&lt;/a&gt; came out.  A really nice service but it's only going to be useful with a high-level programming API. Enter &lt;a href="http://pygooglechart.slowchop.com/"&gt;PyGoogleChart&lt;/a&gt; .. a python interface to generate google chart urls. &lt;/p&gt;
&lt;p&gt;Taking one of my &lt;a href="http://www.perrygeo.net/wordpress/?p=64"&gt;previous example datasets&lt;/a&gt;, here's the 10-second howto:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;blockquote&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;pygooglechart&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;SimpleLineChart&lt;/span&gt;
&lt;span class="n"&gt;chart&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;SimpleLineChart&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;32.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;35.2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;39.9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;40.8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;43.9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;48.2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;50.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;51.9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;53.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;55.9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;60.7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;64.4&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;chart&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;chart&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_url&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;blockquote&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;which gives us:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;http://chart.apis.google.com/chart?cht=lc&amp;chs;=400x200&amp;chd;=t:32.5,35.2,39.9,40.8,43.9,48.2,50.5,51.9,53.1,55.9,60.7,64.4&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;and our chart image:&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="http://chart.apis.google.com/chart?cht=lc&amp;amp;chs=400x200&amp;amp;chd=t:32.5,35.2,39.9,40.8,43.9,48.2,50.5,51.9,53.1,55.9,60.7,64.4"&gt;&lt;/p&gt;</content><category term="articles"></category></entry><entry><title>Geologist vs. Engineer</title><link href="https://www.perrygeo.com/geologist-vs-engineer.html" rel="alternate"></link><published>2007-12-12T00:00:00-07:00</published><updated>2007-12-12T00:00:00-07:00</updated><author><name>Matthew T. Perry</name></author><id>tag:www.perrygeo.com,2007-12-12:/geologist-vs-engineer.html</id><summary type="html">&lt;p&gt;&lt;a href="http://uncyclopedia.org/wiki/Main_Page"&gt;Uncylopedia&lt;/a&gt;, the self-proclaimed encyclopedia "full of misinformation and utter lies", has a hillarious &lt;a href="http://uncyclopedia.org/wiki/Geologist"&gt;article about Geologists&lt;/a&gt;. I especially like the "&lt;a href="http://uncyclopedia.org/wiki/Geologist#The_Great_Geologist-Engineer_Controversy"&gt;Geologist-Engineer Controversy&lt;/a&gt;" which, having worked with both geologists and engineers extensively, is a pretty accurate portrayal of their respective approaches.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Geology, being an art as much as a science, has …&lt;/p&gt;&lt;/blockquote&gt;</summary><content type="html">&lt;p&gt;&lt;a href="http://uncyclopedia.org/wiki/Main_Page"&gt;Uncylopedia&lt;/a&gt;, the self-proclaimed encyclopedia "full of misinformation and utter lies", has a hillarious &lt;a href="http://uncyclopedia.org/wiki/Geologist"&gt;article about Geologists&lt;/a&gt;. I especially like the "&lt;a href="http://uncyclopedia.org/wiki/Geologist#The_Great_Geologist-Engineer_Controversy"&gt;Geologist-Engineer Controversy&lt;/a&gt;" which, having worked with both geologists and engineers extensively, is a pretty accurate portrayal of their respective approaches.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Geology, being an art as much as a science, has always baffled and worried engineers, hence the engineers' defensive weapons of pocket protectors, slide rules, black socks, and eventually computers.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;A related joke:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A geologist and engineer walk into a job interview. They are each asked a simple math question : 'What is 2 times 2?'.  The engineer replies, 'It's 4.00000'. The geologist replies, 'Ah.. it's about 4'&lt;/p&gt;
&lt;/blockquote&gt;</content><category term="articles"></category></entry><entry><title>Quick way to publish a point shapefile to html</title><link href="https://www.perrygeo.com/quick-way-to-publish-a-point-shapefile-to-html.html" rel="alternate"></link><published>2007-12-10T00:00:00-07:00</published><updated>2007-12-10T00:00:00-07:00</updated><author><name>Matthew T. Perry</name></author><id>tag:www.perrygeo.com,2007-12-10:/quick-way-to-publish-a-point-shapefile-to-html.html</id><summary type="html">&lt;p&gt;There are better ways to put data on the web but my latest little project wasn't about the &lt;em&gt;best&lt;/em&gt; way but the quickest way to get some spatial data into the hands of those unfortunate souls who don't have GIS software. The goals were pretty simple:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Take a single point …&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;</summary><content type="html">&lt;p&gt;There are better ways to put data on the web but my latest little project wasn't about the &lt;em&gt;best&lt;/em&gt; way but the quickest way to get some spatial data into the hands of those unfortunate souls who don't have GIS software. The goals were pretty simple:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Take a single point shapefile (or other OGR readable vector data source)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Convert it into html/js that would use one of the web mapping APIs to display the points and all their attributes. &lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The output had to be a standalone, self-contained html file that could be emailed. No server side anything required.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I came up with a quick python hack to do the job (&lt;a href="http://perrygeo.googlecode.com/svn/trunk/gis-bin/shp2Mapstraction.py"&gt;source code&lt;/a&gt;). &lt;a href="http://www.mapstraction.com/"&gt;Mapstraction&lt;/a&gt;, with it's goal of providing a common javascript API for a number of map providers, seemed like an obvious choice.  The python portion of the code reads the shapefile using OGR (you will need the python-gdal bindings, see FWTools) and constructs the html/js. All the javascript is sourced to external URLs so there is no software dependency except for a working network connection. &lt;/p&gt;
&lt;p&gt;This allows for a single command:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;shp2Mapstraction.py bearboxes.shp bearboxes.html Yahoo&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img alt="" src="/assets/img/bearboxes.jpg"&gt;&lt;/p&gt;
&lt;p&gt;which produces &lt;a href="/assets/img/bearboxes.html"&gt;an html file&lt;/a&gt; providing a Yahoo maps interface to the data; in this case the point location of all the bear boxes (food storage lockers to keep your stuff separated from the bears) in the Sierra Nevada. &lt;/p&gt;
&lt;p&gt;Currently it just supports Microsoft Virtual Earth and Yahoo. I had to bypass Google because their key system is restricted by URL. And the mapstraction-to-openlayers connection wasn't working too well though I haven't really investigated.&lt;/p&gt;
&lt;p&gt;Anyways, it provides a quick and easy way to deliver spatial data to anyone with a browser and internet connection. &lt;/p&gt;</content><category term="articles"></category></entry><entry><title>Google Charts - their latest web service</title><link href="https://www.perrygeo.com/google-charts-their-latest-web-service.html" rel="alternate"></link><published>2007-12-06T00:00:00-07:00</published><updated>2007-12-06T00:00:00-07:00</updated><author><name>Matthew T. Perry</name></author><id>tag:www.perrygeo.com,2007-12-06:/google-charts-their-latest-web-service.html</id><summary type="html">&lt;p&gt;Google Charts is a &lt;a href="http://code.google.com/apis/chart/"&gt;web based API&lt;/a&gt; for generating charts/graphs. It supports alot of the common types of graphics including line, pie, bar, scatter plots and Venn diagrams. I've relied on a bunch of other server-side graph generators (&lt;a href="http://www.maptools.org/owtchart/index.phtml"&gt;owtchart&lt;/a&gt;, &lt;a href="http://www.aditus.nu/jpgraph/"&gt;jpgraph&lt;/a&gt;, &lt;a href="http://www.perrygeo.net/wordpress/?p=64"&gt;sparklines&lt;/a&gt;, &lt;a href="http://matplotlib.sourceforge.net/"&gt;matplotlib&lt;/a&gt;, etc) but this looks like it might …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Google Charts is a &lt;a href="http://code.google.com/apis/chart/"&gt;web based API&lt;/a&gt; for generating charts/graphs. It supports alot of the common types of graphics including line, pie, bar, scatter plots and Venn diagrams. I've relied on a bunch of other server-side graph generators (&lt;a href="http://www.maptools.org/owtchart/index.phtml"&gt;owtchart&lt;/a&gt;, &lt;a href="http://www.aditus.nu/jpgraph/"&gt;jpgraph&lt;/a&gt;, &lt;a href="http://www.perrygeo.net/wordpress/?p=64"&gt;sparklines&lt;/a&gt;, &lt;a href="http://matplotlib.sourceforge.net/"&gt;matplotlib&lt;/a&gt;, etc) but this looks like it might be a contender.&lt;/p&gt;
&lt;p&gt;Still there is no higher-level programming API yet ... but give it a few days (interface with numpy anyone?). &lt;a href="http://exilejedi.livejournal.com/189606.html"&gt;ExileJedi blog lists &lt;/a&gt;some other potential disadvantages:&lt;/p&gt;
&lt;blockquote&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;*&lt;/span&gt; You are limited to 50,000 queries per user per day, which may pose some scalability concerns if you plan to build something big on this.
&lt;span class="k"&gt;*&lt;/span&gt; You have to be careful about the number of data points you submit in your request as you can quickly exceed the allowable URL length, and furthermore you might end up with illegibly smooshed-together data points due to the scale of your output.
&lt;span class="k"&gt;*&lt;/span&gt; There&amp;#39;s always the &amp;quot;OMG Google will absorb all our data and become sentient, turn evil, and unleash an army of death robots on us all, run for your lives!&amp;quot; paranoia, but that&amp;#39;s really just silly talk.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;/blockquote&gt;
&lt;p&gt;EDIT:  It appears this service only support GET requests. On one hand you're adding new data so you should be POSTing it, right? On the other hand, you're asking to GET a graphical representation of a set of numerical values. What would a "restful" version of a web graphing API look like? Maybe some of the REST gurus can clear that up.&lt;/p&gt;</content><category term="articles"></category></entry><entry><title>Take the larger view of GIS</title><link href="https://www.perrygeo.com/take-the-larger-view-of-gis.html" rel="alternate"></link><published>2007-12-05T00:00:00-07:00</published><updated>2007-12-05T00:00:00-07:00</updated><author><name>Matthew T. Perry</name></author><id>tag:www.perrygeo.com,2007-12-05:/take-the-larger-view-of-gis.html</id><summary type="html">&lt;p&gt;It's interesting to see the passionate responses to &lt;a href="http://apb.directionsmag.com/archives/3703-Neogeography-is-not-GIS;-not-LI.html"&gt;Joe Francia's article&lt;/a&gt; claiming that neogeography is != GIS.  One one side there is a small group of folks bashing neogeography and claiming the superiority of "GIS". On the other side there is the attitude claiming that some "revolution" has occurred which has …&lt;/p&gt;</summary><content type="html">&lt;p&gt;It's interesting to see the passionate responses to &lt;a href="http://apb.directionsmag.com/archives/3703-Neogeography-is-not-GIS;-not-LI.html"&gt;Joe Francia's article&lt;/a&gt; claiming that neogeography is != GIS.  One one side there is a small group of folks bashing neogeography and claiming the superiority of "GIS". On the other side there is the attitude claiming that some "revolution" has occurred which has supplanted traditional geographic techniques. You'd think there was a cold war going on! Both memes are as wrong as they are arrogant. &lt;/p&gt;
&lt;p&gt;I have always defined GIS as&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Geographic Information System:  The integration of hardware, software, procedures and people to manage the collection, creation, analysis, synthesis, sharing and visualization of spatial information.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Neogeography easily fits that bill. So does Enterprise IT.  So does Desktop mapping. So does Geostatistics. Geodesy. Web Mapping. Remote Sensing. LBS mobile technologies.  Cartography. Surveying. Spatial Analysis and Modeling. Database management. Sensor webs. GPS...  These disciplines are all a small piece of the larger puzzle that is GIS (whether their staunch adherents will admit to it or not!). &lt;/p&gt;
&lt;p&gt;The key word in this controversial acronym is &lt;strong&gt;S&lt;/strong&gt;ystem. In order for any organization to implement a successful GIS, they must figure out a) which technologies will work for them and b) how to integrate them into a coherent whole.  All of these aspects of GIS have something to offer so it's important not to get stuck in a rut with blinders on.  This goes for all "sides" of this ridiculous "neogeo vs GIS" argument. &lt;/p&gt;</content><category term="articles"></category></entry><entry><title>For the cartographers in the house…</title><link href="https://www.perrygeo.com/for-the-cartographers-in-the-house.html" rel="alternate"></link><published>2007-12-04T00:00:00-07:00</published><updated>2007-12-04T00:00:00-07:00</updated><author><name>Matthew T. Perry</name></author><id>tag:www.perrygeo.com,2007-12-04:/for-the-cartographers-in-the-house.html</id><content type="html">&lt;p&gt;Here's another one for the blogrolls:&lt;/p&gt;
&lt;p&gt;&lt;a href="http://strangemaps.wordpress.com/"&gt;http://strangemaps.wordpress.com/&lt;/a&gt;&lt;/p&gt;</content><category term="articles"></category></entry><entry><title>Privacy, Location Technology and Bad Journalism</title><link href="https://www.perrygeo.com/privacy-location-technology-and-bad-journalism.html" rel="alternate"></link><published>2007-11-20T00:00:00-07:00</published><updated>2007-11-20T00:00:00-07:00</updated><author><name>Matthew T. Perry</name></author><id>tag:www.perrygeo.com,2007-11-20:/privacy-location-technology-and-bad-journalism.html</id><summary type="html">&lt;p&gt;The Ventura Star has run an article about &lt;a href="http://www.venturacountystar.com/news/2007/nov/18/where-are-you-in-life-if-you-dont-know-others-do/"&gt;privacy issues and modern geolocation technology&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;As important as this topic is, &lt;a href="http://www.venturacountystar.com/staff/john-moore/contact/"&gt;John Moore&lt;/a&gt; (the author) is clearly uninformed. This is a &lt;em&gt;horrible&lt;/em&gt; piece of journalism. Moore mixes the potential negative effects of various technology such as RFID, cellular communication, sensor networks …&lt;/p&gt;</summary><content type="html">&lt;p&gt;The Ventura Star has run an article about &lt;a href="http://www.venturacountystar.com/news/2007/nov/18/where-are-you-in-life-if-you-dont-know-others-do/"&gt;privacy issues and modern geolocation technology&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;As important as this topic is, &lt;a href="http://www.venturacountystar.com/staff/john-moore/contact/"&gt;John Moore&lt;/a&gt; (the author) is clearly uninformed. This is a &lt;em&gt;horrible&lt;/em&gt; piece of journalism. Moore mixes the potential negative effects of various technology such as RFID, cellular communication, sensor networks, nanotech, community data collection efforts, navigation systems, and GPS into one chilling, over-simplified and baseless viewpoint. Instead of reporting the details of &lt;a href="http://www.geog.ucsb.edu/~good/"&gt;Michael Goodchild's&lt;/a&gt; talk at Ventura College, he treated us to his own paranoid, incoherent vision of the future of technology. Moore's entire premise is based on the fact that: &lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;"GPS is a system that basically allows you to know where you are anywhere in the world within one meter"  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That much is true. He uses this fact to extrapolate the conclusion that GPS allows some nefarious force to monitor your groceries, cell phone calls, and indeed your every movement.&lt;/p&gt;
&lt;p&gt;GPS &lt;em&gt;recieves&lt;/em&gt; satellite signals translates those signals into a location. It takes an entirely different technology to transmit these locations to some third party. I guarantee you that none of my gps tracks have gotten into anyones hands without my consent (come on John Moore, prove me otherwise). &lt;/p&gt;
&lt;p&gt;The title speaks volumes to his ignorance: &lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;"Where are you in life? If you don't know, others using GPS devices do" &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Suggesting that other people with GPS can a) track my movements or b) be tracked by me , shows a complete lack of understanding of the technology. Sure there are privacy dangers. But those dangers must be presented clearly and concisely by someone with half a clue, not this paranoid bullshit journalism. This article would not even pass as a high school essay. &lt;/p&gt;</content><category term="articles"></category></entry><entry><title>Looking for LIDAR services</title><link href="https://www.perrygeo.com/looking-for-lidar-services.html" rel="alternate"></link><published>2007-11-12T00:00:00-07:00</published><updated>2007-11-12T00:00:00-07:00</updated><author><name>Matthew T. Perry</name></author><id>tag:www.perrygeo.com,2007-11-12:/looking-for-lidar-services.html</id><summary type="html">&lt;p&gt;I'm looking for a LIDAR specialist to fly some sensors around San Diego. Ideally we would need someone who could collect both LIDAR data and digital aerial photography (high-res but only visible spectrum), process the data (generate bare-earth DEMs and georeferenced aerials) and deliver it in a GIS-compatible format.   This …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I'm looking for a LIDAR specialist to fly some sensors around San Diego. Ideally we would need someone who could collect both LIDAR data and digital aerial photography (high-res but only visible spectrum), process the data (generate bare-earth DEMs and georeferenced aerials) and deliver it in a GIS-compatible format.   This is in response to the recent fires related to erosion control.. with rainy season coming we'd be on a tight schedule.&lt;/p&gt;
&lt;p&gt;Does anyone have any suggestions of good companies who could provide this service? Please feel free to recommend your own services if you think it would be a good fit.&lt;/p&gt;
&lt;p&gt;You can also contact me directly at perrygeo+lidar at gmail.com&lt;/p&gt;</content><category term="articles"></category></entry><entry><title>Poetics of Cartography</title><link href="https://www.perrygeo.com/poetics-of-cartography.html" rel="alternate"></link><published>2007-10-20T00:00:00-06:00</published><updated>2007-10-20T00:00:00-06:00</updated><author><name>Matthew T. Perry</name></author><id>tag:www.perrygeo.com,2007-10-20:/poetics-of-cartography.html</id><summary type="html">&lt;p&gt;In case you missed the fantastic Chicago public radio program last night on &lt;a href="http://www.thisamericanlife.org/Radio_Episode.aspx?episode=110"&gt;This American Life&lt;/a&gt;,  the NPR-syndicated show did an entire program on "mapping". It goes well beyond the idea of simply mapping our physical infrastructure and really opens up the idea of mapping to the widest possible definition …&lt;/p&gt;</summary><content type="html">&lt;p&gt;In case you missed the fantastic Chicago public radio program last night on &lt;a href="http://www.thisamericanlife.org/Radio_Episode.aspx?episode=110"&gt;This American Life&lt;/a&gt;,  the NPR-syndicated show did an entire program on "mapping". It goes well beyond the idea of simply mapping our physical infrastructure and really opens up the idea of mapping to the widest possible definition; using all our senses to create a multi-dimensional representation of our world. Within the vast experience of life, mapping is described as the abstract process of summarizing and synthesizing a singular slice of that experience.&lt;/p&gt;
&lt;p&gt;The show is available&lt;a href="http://www.thisamericanlife.org/Radio_Episode.aspx?episode=110"&gt; as a stream&lt;/a&gt; and is really worth a listen this weekend.&lt;/p&gt;
&lt;p&gt;P.S. The title of this post comes directly from a quote by Denis Wood, the author of &lt;a href="http://www.amazon.com/Power-Maps-Denis-Wood/dp/0898624932/ref=pd_bbs_2/104-8757092-7919961?ie=UTF8&amp;amp;s=books&amp;amp;qid=1192849090&amp;amp;sr=8-2"&gt;The Power Of Maps&lt;/a&gt; and geographer who is mapping some non-conventional aspects of his neighborhood in Raleigh, North Carolina. The first and arguably most interesting portion of the show from a geographer's standpoint.&lt;/p&gt;</content><category term="articles"></category></entry><entry><title>Turning Ubuntu into a GIS workstation</title><link href="https://www.perrygeo.com/turning-ubuntu-into-a-gis-workstation.html" rel="alternate"></link><published>2007-10-20T00:00:00-06:00</published><updated>2007-10-20T00:00:00-06:00</updated><author><name>Matthew T. Perry</name></author><id>tag:www.perrygeo.com,2007-10-20:/turning-ubuntu-into-a-gis-workstation.html</id><summary type="html">&lt;p&gt;It just keeps getting easier and easier to get a fully functional open source GIS workstation up and running thanks to Ubuntu. The following instructions will take your vanilla installation of &lt;a href="http://www.ubuntu.com/getubuntu"&gt;Ubuntu 7.10&lt;/a&gt; and add the following top-notch desktop GIS applications:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Postgresql/PostGIS : a relational database with vector spatial …&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;</summary><content type="html">&lt;p&gt;It just keeps getting easier and easier to get a fully functional open source GIS workstation up and running thanks to Ubuntu. The following instructions will take your vanilla installation of &lt;a href="http://www.ubuntu.com/getubuntu"&gt;Ubuntu 7.10&lt;/a&gt; and add the following top-notch desktop GIS applications:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Postgresql/PostGIS : a relational database with vector spatial data handling &lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;GRASS : A full blown GIS analysis toolset &lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Quantum GIS: A user-friendly graphical GIS application &lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;GDAL, Proj, Geos : Libraries and utilities for processing spatial data &lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Mapserver : web mapping program and utilites&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Python bindings for QGIS, mapserver and GDAL &lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;GPSBabel : for converting between various GPS formats &lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;R : a high-end statistics package with spatial capabilities &lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;GMT : the Generic Mapping Tools for automated high-quality map output &lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;While this is not a comprehensive list of open source GIS software, these packages cover most of my needs. If you want to live on the bleeding edge and have to have the absolute latest versions, you'll be better off installing these from source. But for those of us that want a stable and highly functional GIS workstation with minimal fuss, this is the way to go:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Go to _ System &amp;gt; Administration &amp;gt; Software Sources _ and make sure the universe and multiverse repositories are turned on. Close the window and the list of available software packages will be refreshed.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Open up a terminal (ie the command line) via _ Applications &amp;gt; Accessories &amp;gt; Terminal_ and type the following:&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;sudo apt-get -y install qgis grass qgis-plugin-grass mapserver-bin gdal-bin cgi-mapserver \
     python-qt4 python-sip4 python-gdal python-mapscript gmt gmt-coastline-data \
     r-recommended gpsbabel shapelib libgdal1-1.4.0-grass&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The &lt;em&gt;sudo&lt;/em&gt; part indicates that the command will be run as the administrator user, _ apt-get -y install_  is the command telling it to install the list of packages and answer yes to any questions that pop up. &lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;There is one package that is worth upgrading to the latest and greatest - Quantum GIS. The latest version (0.9) is due out very shortly and has the ability to write plugins using the python programming language. A big plus! &lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Download the latest build from &lt;a href="http://qgis.org/uploadfiles/testbuilds/qgis0.9.0.debs_ubuntu_gutsy.tar.gz"&gt;http://qgis.org/uploadfiles/testbuilds/qgis0.9.0.debs_ubuntu_gutsy.tar.gz&lt;/a&gt; and extract it ( right-click &amp;gt; Extract Here ).  In the directory you'll see 4 .deb files, only 3 of which you'll need unless you plan on doing any development work.&lt;/p&gt;
&lt;p&gt;Double click libqgis1_0.9.0_i386.deb and you'll get a message saying an older version is available from directly from ubuntu. We already know this so just close and ignore it.  Click &lt;em&gt;Install Package&lt;/em&gt; and wait for it to complete then close out.&lt;/p&gt;
&lt;p&gt;Repeat for qgis_0.9.0_i386.deb and qgis-plugin-grass_0.9.0_i386.deb (in that order).&lt;/p&gt;
&lt;p&gt;And there we have it,  about 15 minutes depending on your internet speed and you've installed a high-end GIS workstation built completely on free and open source software.&lt;/p&gt;</content><category term="articles"></category></entry><entry><title>Update to QGIS Geocoding plugin</title><link href="https://www.perrygeo.com/update-to-qgis-geocoding-plugin.html" rel="alternate"></link><published>2007-10-19T00:00:00-06:00</published><updated>2007-10-19T00:00:00-06:00</updated><author><name>Matthew T. Perry</name></author><id>tag:www.perrygeo.com,2007-10-19:/update-to-qgis-geocoding-plugin.html</id><summary type="html">&lt;p&gt;With the release of QGIS 0.9 imminent , I decided to install in on Windows XP and noticed that &lt;a href="http://www.perrygeo.net/wordpress/?p=60"&gt;the geocoding plugin &lt;/a&gt;was failing... sure enough I had hardcoded linux temporary directories. So I reworked the python code to determine the temp dir in a more cross-platform way (using tempfile …&lt;/p&gt;</summary><content type="html">&lt;p&gt;With the release of QGIS 0.9 imminent , I decided to install in on Windows XP and noticed that &lt;a href="http://www.perrygeo.net/wordpress/?p=60"&gt;the geocoding plugin &lt;/a&gt;was failing... sure enough I had hardcoded linux temporary directories. So I reworked the python code to determine the temp dir in a more cross-platform way (using tempfile.gettempdir() ) and it works fine.&lt;/p&gt;
&lt;p&gt;The update can be downloaded &lt;a href="http://perrygeo.googlecode.com/svn/trunk/qgis/geocode.zip"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Assuming you've installed qgis in the standard location, just unzip this into C:\Program Files\Quantum GIS\python\plugins (windows) or /usr/share/qgis/python/plugins (Linux)  and you should be good to go.  Note that you'll have to create the "plugins" directory if it doesn't exist.&lt;/p&gt;</content><category term="articles"></category></entry><entry><title>CTech software goes multithreaded</title><link href="https://www.perrygeo.com/ctech-software-goes-multithreaded.html" rel="alternate"></link><published>2007-10-12T00:00:00-06:00</published><updated>2007-10-12T00:00:00-06:00</updated><author><name>Matthew T. Perry</name></author><id>tag:www.perrygeo.com,2007-10-12:/ctech-software-goes-multithreaded.html</id><summary type="html">&lt;p&gt;CTech has announced that the next version of it's flagship software package,  &lt;a href="http://www.ctech.com/index.php?page=evspro"&gt;EVS (Environmental Visualization System)&lt;/a&gt;, will take full advantage of multiple processors. &lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="/assets/img/evs.gif"&gt;&lt;/p&gt;
&lt;p&gt;My experience with EVS is mostly in the realm of 3-dimensional kriging and geostatistics. Given the amount of data crunching involved, it's always been sluggish when dealing …&lt;/p&gt;</summary><content type="html">&lt;p&gt;CTech has announced that the next version of it's flagship software package,  &lt;a href="http://www.ctech.com/index.php?page=evspro"&gt;EVS (Environmental Visualization System)&lt;/a&gt;, will take full advantage of multiple processors. &lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="/assets/img/evs.gif"&gt;&lt;/p&gt;
&lt;p&gt;My experience with EVS is mostly in the realm of 3-dimensional kriging and geostatistics. Given the amount of data crunching involved, it's always been sluggish when dealing with a non-trivial amount of data. Nothing is more frustrating that seeing one of your CPU cores cranking away while the others sit idle! But &lt;a href="http://www.ctech.com/forum/viewtopic.php?pid=213#213"&gt;some users are reporting&lt;/a&gt; that the new multithreaded modules get nearly linear performance increases when adding more processing cores.&lt;/p&gt;
&lt;p&gt;CTech is certainly not the first scientific/geostats application to go parallel. But it is the first program that I personally use on a regular basis that will take advantage of a multi-processor system. I hope this marks the beginning of an industry trend in that direction.&lt;/p&gt;</content><category term="articles"></category></entry><entry><title>Autodesk open sources coordinate system software</title><link href="https://www.perrygeo.com/autodesk-open-sources-coordinate-system-software.html" rel="alternate"></link><published>2007-09-25T00:00:00-06:00</published><updated>2007-09-25T00:00:00-06:00</updated><author><name>Matthew T. Perry</name></author><id>tag:www.perrygeo.com,2007-09-25:/autodesk-open-sources-coordinate-system-software.html</id><summary type="html">&lt;p&gt;Not very often do I see open source mentioned on the front page of my Google Finance page (let alone Geospatial Open Source). But here it is.. the announcement was made at FOSS4G2007 that &lt;a href="http://money.cnn.com/news/newsfeeds/articles/prnewswire/AQTU16425092007-1.htm"&gt; autodesk will be open sourcing part of it's coordinate system and map projection technology&lt;/a&gt;. &lt;/p&gt;
&lt;p&gt;So what …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Not very often do I see open source mentioned on the front page of my Google Finance page (let alone Geospatial Open Source). But here it is.. the announcement was made at FOSS4G2007 that &lt;a href="http://money.cnn.com/news/newsfeeds/articles/prnewswire/AQTU16425092007-1.htm"&gt; autodesk will be open sourcing part of it's coordinate system and map projection technology&lt;/a&gt;. &lt;/p&gt;
&lt;p&gt;So what motivation does Autodesk (or any other company) have to open source it's technology? An important line from Lisa Campbell, vice president, Autodesk Geospatial:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;"Our intent to contribute again to the open source community is a reflection of our customers' desire for faster innovation, more frequent product releases, and lower total cost of ownership."&lt;/p&gt;
&lt;/blockquote&gt;</content><category term="articles"></category></entry><entry><title>Parallel python and GIS</title><link href="https://www.perrygeo.com/parallel-python-and-gis.html" rel="alternate"></link><published>2007-09-18T00:00:00-06:00</published><updated>2007-09-18T00:00:00-06:00</updated><author><name>Matthew T. Perry</name></author><id>tag:www.perrygeo.com,2007-09-18:/parallel-python-and-gis.html</id><summary type="html">&lt;p&gt;Let's face it - processing speeds aren't going to be increasing according to Moore's Law anymore; Instead of faster CPUs, &lt;a href="http://www.gotw.ca/publications/concurrency-ddj.htm"&gt;we'll be getting more of them&lt;/a&gt;. The future of programming, it seems to me, lies in the ability to leverage multiple processors. In other words, we have to write parallel code …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Let's face it - processing speeds aren't going to be increasing according to Moore's Law anymore; Instead of faster CPUs, &lt;a href="http://www.gotw.ca/publications/concurrency-ddj.htm"&gt;we'll be getting more of them&lt;/a&gt;. The future of programming, it seems to me, lies in the ability to leverage multiple processors. In other words, we have to write parallel code. Until I read &lt;a href="http://zcologia.com/news/571/catching-up-with-python/"&gt;Seans' post&lt;/a&gt;, I was unware that there was a viable python solution. I had been growing quite dissillusioned by python's dreaded &lt;a href="http://www.pyzine.com/Issue001/Section_Articles/article_ThreadingGlobalInterpreter.html"&gt;Global Interpreter Lock&lt;/a&gt; which confines python to a single processing core. I've even started learning &lt;a href="http://www.erlang.org/"&gt;Erlang&lt;/a&gt; to leverage SMP processing  (until I realized that Erlang and it's standard libraries are virtually useless for anything that needs to handle geospatial data).&lt;/p&gt;
&lt;p&gt;So I gave &lt;a href="http://www.parallelpython.com/"&gt;Parallel Python&lt;/a&gt; (pp) a shot. Since Sean also offered up a bounty for the first GIS application that used pp, I thought it might be a good time to try ;-)&lt;/p&gt;
&lt;p&gt;A good candidate for parallel processing is any application that has to crunch away on lists/arrays of data and whose individual members be handled independently (see &lt;a href="http://www.erlang.org/ml-archive/erlang-questions/200606/msg00130.html"&gt;pmap in Erlang&lt;/a&gt;). I have been working on &lt;a href="http://perrygeo.googlecode.com/svn/trunk/gis-bin/bezier_smooth_pp.py"&gt;an application to smooth linework using bezier curves&lt;/a&gt;. It's not quite polished yet but the image below shows the before and after&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="/assets/img/smoothed.jpg"&gt;&lt;/p&gt;
&lt;p&gt;... but &lt;a href="http://en.wikipedia.org/wiki/B%C3%A9zier_curve"&gt;bezier curves&lt;/a&gt; aren't quite the subject of this post. Let's just say the algorithm takes some time to compute (if you're using a high density of verticies) and can be handled one LineString feature at a time. This makes it a prime candidate for parallelization.&lt;/p&gt;
&lt;p&gt;Given a list of input LineStrings, I could process them the sequential way:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;blockquote&amp;gt;&lt;/span&gt;smooth_lines&lt;span class="w"&gt; &lt;/span&gt;=&lt;span class="w"&gt; &lt;/span&gt;[]
for&lt;span class="w"&gt; &lt;/span&gt;line&lt;span class="w"&gt; &lt;/span&gt;in&lt;span class="w"&gt; &lt;/span&gt;lines:
&lt;span class="w"&gt;    &lt;/span&gt;smooth_lines.append(&lt;span class="w"&gt; &lt;/span&gt;calcBezierFromLine(&lt;span class="w"&gt; &lt;/span&gt;line,&lt;span class="w"&gt; &lt;/span&gt;num_bezpts,&lt;span class="w"&gt; &lt;/span&gt;beztype,&lt;span class="w"&gt; &lt;/span&gt;t)&lt;span class="w"&gt; &lt;/span&gt;)&lt;span class="nt"&gt;&amp;lt;/blockquote&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Or use pp to start up a "job server" which doles the tasks out to as many "workers". A busy worker utilizes a single processing core so a good rule of thumb would be to start up as many workers as you have CPU cores:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;blockquote&amp;gt;&lt;/span&gt;numworkers&lt;span class="w"&gt; &lt;/span&gt;=&lt;span class="w"&gt; &lt;/span&gt;2&lt;span class="w"&gt; &lt;/span&gt;#&lt;span class="w"&gt; &lt;/span&gt;dual-core&lt;span class="w"&gt; &lt;/span&gt;machine
job_server&lt;span class="w"&gt; &lt;/span&gt;=&lt;span class="w"&gt; &lt;/span&gt;pp.Server(numworkers,&lt;span class="w"&gt; &lt;/span&gt;ppservers=ppservers)
smooth_lines&lt;span class="w"&gt; &lt;/span&gt;=&lt;span class="w"&gt; &lt;/span&gt;[]
jobs&lt;span class="w"&gt; &lt;/span&gt;=&lt;span class="w"&gt; &lt;/span&gt;[(line,&lt;span class="w"&gt; &lt;/span&gt;job_server.submit(calcBezierFromLine,&lt;span class="w"&gt; &lt;/span&gt;(line,&lt;span class="w"&gt; &lt;/span&gt;num_bezpts,&lt;span class="w"&gt; &lt;/span&gt;beztype,&lt;span class="w"&gt; &lt;/span&gt;t),&lt;span class="w"&gt; &lt;/span&gt;\
&lt;span class="w"&gt;                             &lt;/span&gt;(computeBezier,&lt;span class="w"&gt; &lt;/span&gt;getPointOnCubicBezier),&lt;span class="w"&gt; &lt;/span&gt;(&amp;quot;numpy&amp;quot;,)&lt;span class="w"&gt; &lt;/span&gt;))&lt;span class="w"&gt;  &lt;/span&gt;for&lt;span class="w"&gt; &lt;/span&gt;line&lt;span class="w"&gt; &lt;/span&gt;in&lt;span class="w"&gt; &lt;/span&gt;lines]
for&lt;span class="w"&gt; &lt;/span&gt;input,&lt;span class="w"&gt; &lt;/span&gt;job&lt;span class="w"&gt; &lt;/span&gt;in&lt;span class="w"&gt; &lt;/span&gt;jobs:
&lt;span class="w"&gt;    &lt;/span&gt;smooth_lines.append(&lt;span class="w"&gt; &lt;/span&gt;job()&lt;span class="w"&gt; &lt;/span&gt;)&lt;span class="nt"&gt;&amp;lt;/blockquote&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Theoretically the parellized version should run twice as fast as the sequential version on my core2 duo machine. And reality was pretty darn close to that:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;blockquote&amp;gt;&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;time&lt;span class="w"&gt; &lt;/span&gt;python&lt;span class="w"&gt; &lt;/span&gt;bezier_smooth_pp.py&lt;span class="w"&gt; &lt;/span&gt;2
Shapefile&lt;span class="w"&gt; &lt;/span&gt;contains&lt;span class="w"&gt; &lt;/span&gt;1114&lt;span class="w"&gt; &lt;/span&gt;lines
Starting&lt;span class="w"&gt; &lt;/span&gt;pp&lt;span class="w"&gt; &lt;/span&gt;with&lt;span class="w"&gt; &lt;/span&gt;2&lt;span class="w"&gt; &lt;/span&gt;workers
Completed&lt;span class="w"&gt; &lt;/span&gt;1114&lt;span class="w"&gt; &lt;/span&gt;new&lt;span class="w"&gt; &lt;/span&gt;lines&lt;span class="w"&gt; &lt;/span&gt;with&lt;span class="w"&gt; &lt;/span&gt;8&lt;span class="w"&gt; &lt;/span&gt;additional&lt;span class="w"&gt; &lt;/span&gt;verticies&lt;span class="w"&gt; &lt;/span&gt;for&lt;span class="w"&gt; &lt;/span&gt;each&lt;span class="w"&gt; &lt;/span&gt;line&lt;span class="w"&gt; &lt;/span&gt;segment&lt;span class="w"&gt; &lt;/span&gt;along&lt;span class="w"&gt; &lt;/span&gt;a&lt;span class="w"&gt; &lt;/span&gt;cubic&lt;span class="w"&gt; &lt;/span&gt;bezier&lt;span class="w"&gt; &lt;/span&gt;curve

real&lt;span class="w"&gt;    &lt;/span&gt;0m10.908s
...

$&lt;span class="w"&gt; &lt;/span&gt;time&lt;span class="w"&gt; &lt;/span&gt;python&lt;span class="w"&gt; &lt;/span&gt;bezier_smooth_pp.py&lt;span class="w"&gt; &lt;/span&gt;1
Shapefile&lt;span class="w"&gt; &lt;/span&gt;contains&lt;span class="w"&gt; &lt;/span&gt;1114&lt;span class="w"&gt; &lt;/span&gt;lines
Starting&lt;span class="w"&gt; &lt;/span&gt;pp&lt;span class="w"&gt; &lt;/span&gt;with&lt;span class="w"&gt; &lt;/span&gt;1&lt;span class="w"&gt; &lt;/span&gt;workers
Completed&lt;span class="w"&gt; &lt;/span&gt;1114&lt;span class="w"&gt; &lt;/span&gt;new&lt;span class="w"&gt; &lt;/span&gt;lines&lt;span class="w"&gt; &lt;/span&gt;with&lt;span class="w"&gt; &lt;/span&gt;8&lt;span class="w"&gt; &lt;/span&gt;additional&lt;span class="w"&gt; &lt;/span&gt;verticies&lt;span class="w"&gt; &lt;/span&gt;for&lt;span class="w"&gt; &lt;/span&gt;each&lt;span class="w"&gt; &lt;/span&gt;line&lt;span class="w"&gt; &lt;/span&gt;segment&lt;span class="w"&gt; &lt;/span&gt;along&lt;span class="w"&gt; &lt;/span&gt;a&lt;span class="w"&gt; &lt;/span&gt;cubic&lt;span class="w"&gt; &lt;/span&gt;bezier&lt;span class="w"&gt; &lt;/span&gt;curve

real&lt;span class="w"&gt;    &lt;/span&gt;0m20.007s
...
&lt;span class="nt"&gt;&amp;lt;/blockquote&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Just think of the possibilities. In the forseeable future, the average computer might have 8+ cores to work with. This could mean that your app will move 8x faster if you parallize the code (assuming there are no IO or bandwidth bottlenecks). I'd love to test it out on a system with more than 2 processing cores but, unfortunately, I don't have access to any &lt;a href="http://www.calvin.edu/~adams/research/microwulf/"&gt;beowulf clusters&lt;/a&gt;, &lt;a href="http://www.sun.com/processors/UltraSPARC-T1/"&gt; Sun UltraSparc servers,&lt;/a&gt; or &lt;a href="http://www.apple.com/macpro/"&gt;8-core Xeon Mac Pros&lt;/a&gt;. This is what I &lt;em&gt;really need&lt;/em&gt; to complete my research ;-) So if anyone want to donate to the cause, send me an email! &lt;/p&gt;
&lt;p&gt;And to answer Sean's bounty, I don't consider this an actual application (yet) but I hope it can spur some interest and move things in that direction. But if you feel the need to send me some New Belgium swag (or one of the machines listed above), feel free ;-)&lt;/p&gt;</content><category term="articles"></category></entry><entry><title>The world turned right-side up</title><link href="https://www.perrygeo.com/the-world-turned-right-side-up.html" rel="alternate"></link><published>2007-09-05T00:00:00-06:00</published><updated>2007-09-05T00:00:00-06:00</updated><author><name>Matthew T. Perry</name></author><id>tag:www.perrygeo.com,2007-09-05:/the-world-turned-right-side-up.html</id><summary type="html">&lt;p&gt;I've been working alot in &lt;a href="http://www.goldensoftware.com/products/surfer/surfer.shtml"&gt;Surfer&lt;/a&gt; these days; an excellent geostats and surface mapping package. I was very happy to find that GDAL read it's .grd binary format until I noticed the output from gdalinfo:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;&amp;gt; &lt;/span&gt;&lt;span class="ge"&gt;C:\Workspace\Temp\interpolation&amp;gt;gdalinfo svpce_5.grd&lt;/span&gt;
Driver: GS7BG/Golden Software 7 Binary Grid (.grd …&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</summary><content type="html">&lt;p&gt;I've been working alot in &lt;a href="http://www.goldensoftware.com/products/surfer/surfer.shtml"&gt;Surfer&lt;/a&gt; these days; an excellent geostats and surface mapping package. I was very happy to find that GDAL read it's .grd binary format until I noticed the output from gdalinfo:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;&amp;gt; &lt;/span&gt;&lt;span class="ge"&gt;C:\Workspace\Temp\interpolation&amp;gt;gdalinfo svpce_5.grd&lt;/span&gt;
Driver: GS7BG/Golden Software 7 Binary Grid (.grd)
Files: svpce_5.grd
Size is 555, 339
Coordinate System is `&amp;#39;
Origin = (383371.000000000000000,3764907.000000000000000)
Pixel Size = (0.500000000000000,0.500000000000000)
Corner Coordinates:
Upper Left  (  383371.000, 3764907.000)
Lower Left  (  383371.000, 3765076.500)
Upper Right (  383648.500, 3764907.000)
Lower Right (  383648.500, 3765076.500)
Center      (  383509.750, 3764991.750)
Band 1 Block=555x1 Type=Float64, ColorInterp=Undefined
 NoData Value=1.70141e+038
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Notice that upper Y value is &lt;em&gt;south&lt;/em&gt; of the lower Y value! Basically the raster lines order is reversed (bottom-to-top instead of the normal raster orientation of top-to-bottom). I've also experienced the same issue with some NetCDF files so I thought it would be good to have a generic solution to the problem.&lt;/p&gt;
&lt;p&gt;So I hacked up the gdal_merge.py script (distributed with gdal, fwtools, etc) and created a raster flip script that will invert the image along the y axis and retain the georeferencing and metadata. The resulting &lt;a href="http://perrygeo.googlecode.com/svn/trunk/gis-bin/flip_raster.py"&gt;flip_raster.py&lt;/a&gt; script seems to work pretty well though it is far from tested.&lt;/p&gt;
&lt;p&gt;Here's an example:&lt;/p&gt;
&lt;p&gt;The standard gdal_translate method (which doesn't account for the inverted coordinate space):&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;gdal_translate -of GTiff krig1.grd krig1_translate.tif&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img alt="" src="/assets/img/standard.jpg"&gt;&lt;/p&gt;
&lt;p&gt;And the flipped raster method:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;flip_raster.py -o krig1_flip.tif -of GTiff krig1.grd &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img alt="" src="/assets/img/flipped.jpg"&gt;&lt;/p&gt;
&lt;p&gt;And we're good.  gdalinfo confirms that we have the same extents, pixel sizes, metadata, etc as the original dataset. &lt;/p&gt;</content><category term="articles"></category></entry><entry><title>Mapserver vs Mapnik revisited</title><link href="https://www.perrygeo.com/mapserver-vs-mapnik-revisited.html" rel="alternate"></link><published>2007-09-04T00:00:00-06:00</published><updated>2007-09-04T00:00:00-06:00</updated><author><name>Matthew T. Perry</name></author><id>tag:www.perrygeo.com,2007-09-04:/mapserver-vs-mapnik-revisited.html</id><summary type="html">&lt;p&gt;A while ago, I was enamored with mapnik's image quality despite it's limitations compared to the vast configurability of the mapserver mapfile. Now that mapserver uses the AGG rendering library,  it might not be necessary to compromise configurability in order to get beautiful linework. I just installed the recent beta …&lt;/p&gt;</summary><content type="html">&lt;p&gt;A while ago, I was enamored with mapnik's image quality despite it's limitations compared to the vast configurability of the mapserver mapfile. Now that mapserver uses the AGG rendering library,  it might not be necessary to compromise configurability in order to get beautiful linework. I just installed the recent beta of mapserver 5.0 and the image quality is very crisp... but this comes at the expense of rendering speed.&lt;/p&gt;
&lt;p&gt;All the times below are the average of ten runs using a full global view of a simplified shapefile of country borders. &lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="/assets/img/mapserver_gd_test.jpg"&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;mapserver (gd) : 0.082 sec , 18kb&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;OUTPUTFORMAT
  NAME "GD_JPEG"
  DRIVER "GD/JPEG"
  MIMETYPE "image/jpeg"
  IMAGEMODE RGB
  EXTENSION "jpg"
END&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;shp2img -m test.map -o mapserver_gd_test.jpg&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="/assets/img/mapserver_agg_test.jpg"&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;mapserver (agg) : 0.188 sec , 16kb&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;IMAGEQUALITY 80 
OUTPUTFORMAT
  NAME 'AGG_JPEG'
  DRIVER AGG/JPEG
  IMAGEMODE RGB
END&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;Note that if we bump up imagequality to 90% to (roughly) match the mapnik image, the rendering time and size increase a bit (.201 sec, 25kb)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;shp2img -m test.map -o mapserver_agg_test.jpg&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="/assets/img/mapnik_output.jpg"&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;mapnik (agg) : 0.282 sec, 23kb&lt;/strong&gt;
python test_mapnik.py&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Running this through the python interpreter is likely to interfere with the speed of the results so these times may not be very comparable to shp2img.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Using these preliminary results, it looks like mapserver 5.0 with AGG rendering is roughly equal to mapnik based on a balance of quality/speed/image size. But since I'd prefer to use mapfiles over the undocumented mapnik xml format any day, I think I'll stick with my beloved mapserver. Kudos to the mapserver developers for raising the bar once again.&lt;/p&gt;</content><category term="articles"></category></entry><entry><title>Performance testing rasters with mapserver</title><link href="https://www.perrygeo.com/performance-testing-rasters-with-mapserver.html" rel="alternate"></link><published>2007-09-04T00:00:00-06:00</published><updated>2007-09-04T00:00:00-06:00</updated><author><name>Matthew T. Perry</name></author><id>tag:www.perrygeo.com,2007-09-04:/performance-testing-rasters-with-mapserver.html</id><summary type="html">&lt;p&gt;There's been some good talk on the mapserver list (thanks to Gregor's diligent testing) about performance related to serving up raster imagery. &lt;/p&gt;
&lt;p&gt;First off, comparisons of &lt;a href="http://lists.umn.edu/cgi-bin/wa?A2=ind0709&amp;amp;L=mapserver-users&amp;amp;T=0&amp;amp;O=D&amp;amp;P=1526"&gt;image&lt;/a&gt; &lt;a href="http://lists.umn.edu/cgi-bin/wa?A2=ind0709&amp;amp;L=mapserver-users&amp;amp;T=0&amp;amp;O=D&amp;amp;P=1526"&gt;formats. &lt;/a&gt;Then a look at some TIFF &lt;a href="http://lists.umn.edu/cgi-bin/wa?A2=ind0709&amp;amp;L=mapserver-users&amp;amp;T=0&amp;amp;O=D&amp;amp;P=2214"&gt;optimization&lt;/a&gt; &lt;a href="http://lists.umn.edu/cgi-bin/wa?A2=ind0709&amp;amp;L=mapserver-users&amp;amp;T=0&amp;amp;O=D&amp;amp;P=4492"&gt;techniques&lt;/a&gt; like overviews (similar to "pyramids" in ESRI land) and internal tiling to boost rendering …&lt;/p&gt;</summary><content type="html">&lt;p&gt;There's been some good talk on the mapserver list (thanks to Gregor's diligent testing) about performance related to serving up raster imagery. &lt;/p&gt;
&lt;p&gt;First off, comparisons of &lt;a href="http://lists.umn.edu/cgi-bin/wa?A2=ind0709&amp;amp;L=mapserver-users&amp;amp;T=0&amp;amp;O=D&amp;amp;P=1526"&gt;image&lt;/a&gt; &lt;a href="http://lists.umn.edu/cgi-bin/wa?A2=ind0709&amp;amp;L=mapserver-users&amp;amp;T=0&amp;amp;O=D&amp;amp;P=1526"&gt;formats. &lt;/a&gt;Then a look at some TIFF &lt;a href="http://lists.umn.edu/cgi-bin/wa?A2=ind0709&amp;amp;L=mapserver-users&amp;amp;T=0&amp;amp;O=D&amp;amp;P=2214"&gt;optimization&lt;/a&gt; &lt;a href="http://lists.umn.edu/cgi-bin/wa?A2=ind0709&amp;amp;L=mapserver-users&amp;amp;T=0&amp;amp;O=D&amp;amp;P=4492"&gt;techniques&lt;/a&gt; like overviews (similar to "pyramids" in ESRI land) and internal tiling to boost rendering speed. &lt;/p&gt;
&lt;p&gt;Most of the conclusions are not all that staggering: &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;TIFF is fastest but takes up more space compared to ECW and JPEG2000. &lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Overviews speed up TIFFs tremendously when zoomed out (ie when mapserver would otherwise have to perform some heavy downsampling) &lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Internal tiles in GeoTIFF format give a boost when zoomed in (only the necessary tiles are read from disk) &lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The TIFF comparison was run on two setups; a monsterous 8-core, RAID-5 equipped beast and a low-memory virtual machine on low-end PC hardware. The TIFF optimizations are very noticeable on the lesser machine but almost completely negligible on the high-end machine. &lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;Both tiling and overviews are useful, but only on machines with resource 
shortages, such as slow disks or a lack of spare RAM for caching.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Nothing earth-shattering (these techniques are often mentioned as best practices) but is very nice to see some hard numbers to back it up.  Plus the verbose test logs provide a good example for a newbie trying to implement them. Good stuff Gregor!&lt;/p&gt;</content><category term="articles"></category></entry><entry><title>Mapping the Undesirable</title><link href="https://www.perrygeo.com/mapping-the-undesirable.html" rel="alternate"></link><published>2007-08-28T00:00:00-06:00</published><updated>2007-08-28T00:00:00-06:00</updated><author><name>Matthew T. Perry</name></author><id>tag:www.perrygeo.com,2007-08-28:/mapping-the-undesirable.html</id><summary type="html">&lt;p&gt;While by no means a new phenomenon, &lt;a href="http://www.thevision2020.com/LocateSexOffenders.aspx"&gt; Vision 20/20&lt;/a&gt; is offering a service allowing you to see a map of the registered sex offenders in your area. WorldChanging, one of my favorite blogs on emerging technologies, has a great article discussing the issues surrounding &lt;a href="http://www.worldchanging.com/archives/007189.html"&gt; mapping of sex offenders &lt;/a&gt;.  &lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Is …&lt;/p&gt;&lt;/blockquote&gt;</summary><content type="html">&lt;p&gt;While by no means a new phenomenon, &lt;a href="http://www.thevision2020.com/LocateSexOffenders.aspx"&gt; Vision 20/20&lt;/a&gt; is offering a service allowing you to see a map of the registered sex offenders in your area. WorldChanging, one of my favorite blogs on emerging technologies, has a great article discussing the issues surrounding &lt;a href="http://www.worldchanging.com/archives/007189.html"&gt; mapping of sex offenders &lt;/a&gt;.  &lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Is this sort of service, based on powerful networked technologies -- and one being sold on the basis of fear -- an appropriate use of the technology? Where is the data being sourced from? How are the people inputting it being supervised? And what rights to privacy and presumptions of innocence are the people it tracks entitled to? &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;These are good points, but even more disturbing to me as a citizen and a GIS professional, is that these maps use geocoding services that are &lt;a href="http://www.ij-healthgeographics.com/content/2/1/10/abstract/"&gt; not nearly accurate enough&lt;/a&gt; for the scale at which they are being viewed. Even in suburban areas, using linear-referenced geocoding techniques can still yield errors of 100s of meters! The margin of error in the geocoding engine alone is enough to place the sex offender icon directly on an innocent citizens' home.&lt;/p&gt;
&lt;p&gt;For instance, which of the homes in the map below is the residence of a sex offender? Does the ambiguity bother you? Would it matter more if &lt;em&gt;you&lt;/em&gt; were the innocent person living next door?&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="/assets/img/offender.png"&gt;&lt;/p&gt;
&lt;p&gt;For maps with this much social weight, I think that a bit more diligence is due to ensure that this data is as accurate as it needs to be! &lt;/p&gt;</content><category term="articles"></category></entry><entry><title>Zaca Lake Fire Map</title><link href="https://www.perrygeo.com/zaca-lake-fire-map.html" rel="alternate"></link><published>2007-08-03T00:00:00-06:00</published><updated>2007-08-03T00:00:00-06:00</updated><author><name>Matthew T. Perry</name></author><id>tag:www.perrygeo.com,2007-08-03:/zaca-lake-fire-map.html</id><summary type="html">&lt;p&gt;&lt;img alt="" src="/assets/img/viewfromwest027.jpg"&gt;&lt;/p&gt;
&lt;p&gt;Ah the joys of living in southern california. The Zaca Lake fire has been burning since July 4th and recently &lt;a href="http://independent.com/news/2007/aug/03/zaca-fire-explodes/"&gt;flared up again&lt;/a&gt; with a shift in winds which is blowing ash and a very ominous plume of smoke all over downtown santa barbara. While it's still burning in the …&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;img alt="" src="/assets/img/viewfromwest027.jpg"&gt;&lt;/p&gt;
&lt;p&gt;Ah the joys of living in southern california. The Zaca Lake fire has been burning since July 4th and recently &lt;a href="http://independent.com/news/2007/aug/03/zaca-fire-explodes/"&gt;flared up again&lt;/a&gt; with a shift in winds which is blowing ash and a very ominous plume of smoke all over downtown santa barbara. While it's still burning in the wilderness areas north of town, the Paradise Road area along the Santa Ynez river has been evacuated. &lt;a href="http://maps.google.com/maps/ms?ie=UTF8&amp;amp;hl=en&amp;amp;msa=0&amp;amp;msid=105524280382284020010.0004351434f7c4b6bb5eb&amp;amp;ll=34.787162,-120.029583&amp;amp;spn=0.137739,0.144711&amp;amp;t=h&amp;amp;z=13&amp;amp;om=1"&gt;Check it out on google maps&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The Santa Barbara News Press is reporting the fire has reached 39,000 acres and has cost $43 million thus far to contain. The county supervisors are likely to declare a state of emergency and there is already a health warning in effect. So much for my bike ride this afternoon...&lt;/p&gt;</content><category term="articles"></category></entry><entry><title>Desktop vs Web UI</title><link href="https://www.perrygeo.com/desktop-vs-web-ui.html" rel="alternate"></link><published>2007-06-11T00:00:00-06:00</published><updated>2007-06-11T00:00:00-06:00</updated><author><name>Matthew T. Perry</name></author><id>tag:www.perrygeo.com,2007-06-11:/desktop-vs-web-ui.html</id><summary type="html">&lt;p&gt;This might be a dup story for some but I thought it was interesting enough to post nonetheless:&lt;/p&gt;
&lt;p&gt;Jeff Atwood wrote an interesting piece about Desktop vs Web UI that is directly relevant to mapping : &lt;a href="http://www.codinghorror.com/blog/archives/000883.html"&gt;Who Killed the Desktop Application?&lt;/a&gt;. He compares the usability of Microsoft Streets and Trips with …&lt;/p&gt;</summary><content type="html">&lt;p&gt;This might be a dup story for some but I thought it was interesting enough to post nonetheless:&lt;/p&gt;
&lt;p&gt;Jeff Atwood wrote an interesting piece about Desktop vs Web UI that is directly relevant to mapping : &lt;a href="http://www.codinghorror.com/blog/archives/000883.html"&gt;Who Killed the Desktop Application?&lt;/a&gt;. He compares the usability of Microsoft Streets and Trips with Google Maps and concludes &lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;All the innovation in user interface seems to be taking place on the web, and desktop applications just aren't keeping up. &lt;/p&gt;
&lt;/blockquote&gt;</content><category term="articles"></category></entry><entry><title>OGR and matplotlib examples</title><link href="https://www.perrygeo.com/ogr-and-matplotlib-examples.html" rel="alternate"></link><published>2007-06-10T00:00:00-06:00</published><updated>2007-06-10T00:00:00-06:00</updated><author><name>Matthew T. Perry</name></author><id>tag:www.perrygeo.com,2007-06-10:/ogr-and-matplotlib-examples.html</id><content type="html">&lt;p&gt;Jose Gomez-Dans posted a great example of using OGR, Postgis and Matplotlib with Python - &lt;a href="http://jgomezdans.googlepages.com/ogr%2Cpythonymatplotlib"&gt;OGR, Python y Matplotlib&lt;/a&gt; (Spanish only).&lt;/p&gt;</content><category term="articles"></category></entry><entry><title>FDO, GDAL/OGR and FME ?</title><link href="https://www.perrygeo.com/fdo-gdalogr-and-fme.html" rel="alternate"></link><published>2007-05-31T00:00:00-06:00</published><updated>2007-05-31T00:00:00-06:00</updated><author><name>Matthew T. Perry</name></author><id>tag:www.perrygeo.com,2007-05-31:/fdo-gdalogr-and-fme.html</id><content type="html">&lt;p&gt;&lt;a href="http://fdo.osgeo.org/"&gt;FDO&lt;/a&gt;, &lt;a href="http://gdal.osgeo.org/"&gt;GDAL&lt;/a&gt; and &lt;a href="http://safe.com/products/fme/index.php"&gt;FME&lt;/a&gt; all seem to operate in roughly the same domain - Providing a data model, API and tools to translate between spatial data formats. Does anyone know of any good write-ups comparing/contrasting the features of these three libraries?   &lt;/p&gt;</content><category term="articles"></category></entry><entry><title>QGIS Geocoding plugin</title><link href="https://www.perrygeo.com/qgis-geocoding-plugin.html" rel="alternate"></link><published>2007-05-28T00:00:00-06:00</published><updated>2007-05-28T00:00:00-06:00</updated><author><name>Matthew T. Perry</name></author><id>tag:www.perrygeo.com,2007-05-28:/qgis-geocoding-plugin.html</id><summary type="html">&lt;p&gt;A few weeks back, I decided to take the plunge and learn the python bindings for QGIS 0.9. My first experiment was to implement a geocoder plugin. What started mostly as a learning experiment turned into something that might actually be useful!&lt;/p&gt;
&lt;p&gt;The idea was to use web services …&lt;/p&gt;</summary><content type="html">&lt;p&gt;A few weeks back, I decided to take the plunge and learn the python bindings for QGIS 0.9. My first experiment was to implement a geocoder plugin. What started mostly as a learning experiment turned into something that might actually be useful!&lt;/p&gt;
&lt;p&gt;The idea was to use web services to do all the actual geocoding work (the hard part!) and the delimited text provider to load the results into qgis. Right now it's built on top of the &lt;a href="http://developer.yahoo.com/maps/rest/V1/geocode.html"&gt;Yahoo geocoder&lt;/a&gt; which is, IMO, the best out there.. very flexible about the input format. The &lt;a href="http://exogen.case.edu/projects/geopy/"&gt;geopy module&lt;/a&gt; is used to interact with the geocoding services so it could potentially support other engines such as geocoder.us, virtual earth, google, etc. &lt;/p&gt;
&lt;p&gt;The user interface is very straightforward; enter list of addresses/placenames seperated by a line break, pick an output file and go. To be legitimate, you should also sign up for a yahoo api key, though the 'YahooDemo' key will work ok for testing purposes.&lt;/p&gt;
&lt;p&gt;&lt;a href="/assets/img/dialog.jpg"&gt;&lt;img alt="" src="/assets/img/dialog_thumb.jpg"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="/assets/img/result.jpg"&gt;&lt;img alt="" src="/assets/img/result_thumb.jpg"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Here's the install process (assuming you already have &lt;a href="http://www.reprojected.com/presentations/Videos/qgis_install_051407/install_qgis.txt"&gt;python, pyqt4, qgis 0.9, qgis bindings, etc. set up&lt;/a&gt;):&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;svn checkout http://perrygeo.googlecode.com/svn/trunk/qgis/geocode
     cd geocode
     emacs Makefile # change install directory if needed
     sudo make install&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This is just a rough cut and it's my first attempt at using the qgis and qt apis so there are probably many things that could be improved upon. Ideally this plugin could:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Parse text files as input &lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Allow for a choice of geocoding engine &lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;??? &lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Feedback (and patches) welcome ;-)&lt;/p&gt;</content><category term="articles"></category></entry><entry><title>Python gpsd bindings</title><link href="https://www.perrygeo.com/python-gpsd-bindings.html" rel="alternate"></link><published>2007-05-27T00:00:00-06:00</published><updated>2007-05-27T00:00:00-06:00</updated><author><name>Matthew T. Perry</name></author><id>tag:www.perrygeo.com,2007-05-27:/python-gpsd-bindings.html</id><summary type="html">&lt;p&gt;If you want to get a linux/unix machine talking to your GPS unit, most likely you'll be using &lt;a href="http://gpsd.berlios.de/"&gt;gpsd&lt;/a&gt;. There are many great apps that build off of gpsd such as kismet and gpsdrive. &lt;/p&gt;
&lt;p&gt;Installing gpsd on debian/ubuntu systems is as simple as &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;sudo apt-get install gpsd gpsd-clients …&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</summary><content type="html">&lt;p&gt;If you want to get a linux/unix machine talking to your GPS unit, most likely you'll be using &lt;a href="http://gpsd.berlios.de/"&gt;gpsd&lt;/a&gt;. There are many great apps that build off of gpsd such as kismet and gpsdrive. &lt;/p&gt;
&lt;p&gt;Installing gpsd on debian/ubuntu systems is as simple as &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;sudo apt-get install gpsd gpsd-clients
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;You should be able to connect your gps via serial port and start a gpsd server &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;sudo gpsd /dev/ttyS0
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The gpsd server reads NMEA sentences from the gps unit and is accessed on port 2947. You can test if everything is working by running a pre-built gpsd client such as xgps.&lt;/p&gt;
&lt;p&gt;This is very useful for situations where you need lower-level access to the gps data; for logging your position to a postgres database for example. The debian packages (and most others I'm assuming) come with gps.py, a python interface to gpsd allowing you to pull your lat/long from the gps in real time. This opens the door for all sorts of neat real-time gps apps.&lt;/p&gt;
&lt;blockquote&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;gps&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;os&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;time&lt;/span&gt;

&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;gps&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;gps&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;while&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;system&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;clear&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;admosy&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;# a = altitude, d = date/time, m=mode,  &lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;# o=postion/fix, s=status, y=satellites&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;print&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39; GPS reading&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;----------------------------------------&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;latitude    &amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fix&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;latitude&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;longitude   &amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fix&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;longitude&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;time utc    &amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fix&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;altitude    &amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fix&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;altitude&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;eph         &amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fix&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;eph&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;epv         &amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fix&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;epv&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;ept         &amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fix&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ept&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;speed       &amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fix&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;speed&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;climb       &amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fix&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;climb&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;print&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39; Satellites (total of&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;satellites&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39; in view)&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;satellites&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="se"&gt;\t&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;/blockquote&gt;
&lt;p&gt;... which gives you a simple readout to the terminal every 3 seconds.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="/assets/img/gpsd_python.jpg"&gt;&lt;/p&gt;
&lt;p&gt;Obviously there are much more interesting applications for this ( logging data to postgis, displaying real-time tracking data in QGIS via a python plugin, etc). But this is a good start for any python based app.&lt;/p&gt;</content><category term="articles"></category></entry><entry><title>Sparklines in python</title><link href="https://www.perrygeo.com/sparklines-in-python.html" rel="alternate"></link><published>2007-05-19T00:00:00-06:00</published><updated>2007-05-19T00:00:00-06:00</updated><author><name>Matthew T. Perry</name></author><id>tag:www.perrygeo.com,2007-05-19:/sparklines-in-python.html</id><summary type="html">&lt;p&gt;Edward Tufte, the outspoken guru of data visualization, has long been an advocate of clear and concise (almost minimalist) graphical representations of data. He's got a lot of great ideas relevant to cartography (my cartography course at Humboldt State used his book "The Visual Display of Quantitative Information" as our …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Edward Tufte, the outspoken guru of data visualization, has long been an advocate of clear and concise (almost minimalist) graphical representations of data. He's got a lot of great ideas relevant to cartography (my cartography course at Humboldt State used his book "The Visual Display of Quantitative Information" as our text). &lt;/p&gt;
&lt;p&gt;One of the coolest ideas are "sparklines" which he describes as "data-intense, design-simple, word-sized graphics". Instead of standalone charts that are often placed on their own and separate from the text that discusses them, sparklines are meant to be placed in-line with the text and provide memorable, simple and contextually-relevant data to support the surrounding text. For example:&lt;/p&gt;
&lt;p&gt;_The US National Debt as a percentage of GDP increased during the Reagan and Bush presidencies &lt;img alt="" src="/assets/img/reaganbush.GIF"&gt; but dropped off slightly during the Clinton administration  &lt;img alt="" src="/assets/img/clinton.GIF"&gt; . _&lt;/p&gt;
&lt;p&gt;Now of course I had to figure out how to produce these in python.  Theres a great &lt;a href="http://bitworking.org/projects/sparklines/#source"&gt;cgi application&lt;/a&gt;, written in python by Joe Gregorio, that does sparklines. I needed something that was abstracted away from the CGI framework, more of a proper python module. Replacing all the CGI-specific code was straightforward and I came up with a standalone sparkline python module (&lt;a href="http://perrygeo.googlecode.com/svn/trunk/gis-bin/spark.py"&gt; View / Download the Source Code. &lt;/a&gt; ) The only dependencies are python and the python imaging library.&lt;/p&gt;
&lt;p&gt;In the minimalist spirt of sparklines, the interface was kept simple. First you create a list of data values then simply pass the list to one of the sparkline generators:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;import spark
a = [32.5,35.2,39.9,40.8,43.9,48.2,50.5,51.9,53.1,55.9,60.7,64.4]
spark.sparkline_smooth(a).show()&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Or if you prefer a more discrete, bar-graph-style &lt;img alt="" src="/assets/img/discrete.GIF"&gt; instead of a smooth line:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;spark.sparkline_discrete(a).show()&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;There's plenty of room for configuration. For example, in the national debt example above I wanted to keep the y axis at the same scale (instead of the default min-max scaling) and make each step 6 pixels wide:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;spark.sparkline_smooth(a, dmin=30,dmax=70, step=6).show()&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;How does this relate to cartography? GIS typically takes a snapshot representation of earth, frozen in time. Since sparklines seem particularly good at representing change-over-time, it could be an interesting way to add a time dimension to a 2-D map. For example, instead of just displaying country polygons with labels, you could place a sparkline right under the label showing the population changes over the last century. It seems like it would be an ideal way to embed alot of useful information into a small map. &lt;/p&gt;
&lt;p&gt;Anyone know of any good examples?&lt;/p&gt;</content><category term="articles"></category></entry><entry><title>Blessed Unrest - Paul Hawken’s presentation</title><link href="https://www.perrygeo.com/blessed-unrest-paul-hawkens-presentation.html" rel="alternate"></link><published>2007-05-14T00:00:00-06:00</published><updated>2007-05-14T00:00:00-06:00</updated><author><name>Matthew T. Perry</name></author><id>tag:www.perrygeo.com,2007-05-14:/blessed-unrest-paul-hawkens-presentation.html</id><summary type="html">&lt;p&gt;I got the chance to see Paul Hawken speak tonight in Santa Barbara. I knew him best as the author of &lt;a href="http://www.natcap.org/"&gt;Natural Capitalism&lt;/a&gt; which provided a great roadmap for integrating ecologically sustainable practices with the business world. This talk was based on his recent book - &lt;a href="http://blessedunrest.com/"&gt;Blessed Unrest - How the Largest …&lt;/a&gt;&lt;/p&gt;</summary><content type="html">&lt;p&gt;I got the chance to see Paul Hawken speak tonight in Santa Barbara. I knew him best as the author of &lt;a href="http://www.natcap.org/"&gt;Natural Capitalism&lt;/a&gt; which provided a great roadmap for integrating ecologically sustainable practices with the business world. This talk was based on his recent book - &lt;a href="http://blessedunrest.com/"&gt;Blessed Unrest - How the Largest Movement in the World Came into Being and Why No One Saw It Coming&lt;/a&gt;.  &lt;/p&gt;
&lt;p&gt;The basis of this book is simple: that organically-developed, bottom-up, non-hierarchical organizations (which number in the millions according to his research) are now leading the world in many diverse areas of service. He describes these environmental and social justice organizations as the "immune system" of our societies; our response to destructive and corrupt habits perpetrated by those in power who are willing to compromise our future for short-term gain. &lt;/p&gt;
&lt;p&gt;One thing that struck me about the subject was the importance of sharing &lt;em&gt;information&lt;/em&gt; and &lt;em&gt;ideas&lt;/em&gt; (as opposed to spreading an &lt;em&gt;ideology&lt;/em&gt;). I thought one of the most interesting stories of the night was his description of how the meme of non-violent civil disobedience evolved... from Emerson, to Thoreau, to Ghandi, to Rosa Parks to Martin Luther King, Jr. At each turn of the story, there was someone (often unnamed but vitally important) who turned on each of these people to the ideas of those who came before. &lt;/p&gt;
&lt;p&gt;Paul was eager to point out the role of technology in this inter-connected mesh of grassroots community organizations. He mentioned open-source software a few times and even gave a shout out to Ruby on Rails (which I gather was the backbone for his &lt;a href="http://wiserearth.org/"&gt;WiserEarth.org&lt;/a&gt; site focussed on connecting these diverse organizations).&lt;/p&gt;
&lt;p&gt;It was a careful mix of optimism and pessimism; Paul was careful in noting the many severe challenges we've been handed but was confident that this bottom-up mesh of interconnected citizens can form a community strong enough to withstand anything that comes it's way. In the end, his message was about doing what you love, connecting with others and standing up for your values. Sounds like good advice to me.&lt;/p&gt;</content><category term="articles"></category></entry><entry><title>Cleaning up CAD data with postgis</title><link href="https://www.perrygeo.com/cleaning-up-cad-data-with-postgis.html" rel="alternate"></link><published>2007-05-14T00:00:00-06:00</published><updated>2007-05-14T00:00:00-06:00</updated><author><name>Matthew T. Perry</name></author><id>tag:www.perrygeo.com,2007-05-14:/cleaning-up-cad-data-with-postgis.html</id><summary type="html">&lt;p&gt;Don't you just love getting CAD data into GIS! I received a .dwg file with study areas delineated as polylines which we needed as polygons for analysis purposes. And it wasn't just one polyline surrounding each study area ... there were hundreds of little line segments which outlined a couple dozen …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Don't you just love getting CAD data into GIS! I received a .dwg file with study areas delineated as polylines which we needed as polygons for analysis purposes. And it wasn't just one polyline surrounding each study area ... there were hundreds of little line segments which outlined a couple dozen areas (what was this CAD tech thinking?) . Luckily each segment had a name to associate it with the proper area.&lt;/p&gt;
&lt;p&gt;I found that ArcMap's tools for doing this are painfully inadequate so I turned to postgis. After converting the dataset to a shapefile, the solution was simple:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;shp2pgsql "study_areas.shp" areas | psql -d gisdata
pgsql2shp -f "study_areas_poly.shp" gisdata \
   "SELECT BuildArea(collect(the_geom)) AS the_geom, name 
    FROM areas 
    GROUP by name"&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Viola... a new shapefile with my proper polygons instead of CAD chicken scratch. &lt;/p&gt;</content><category term="articles"></category></entry><entry><title>Back on the train</title><link href="https://www.perrygeo.com/back-on-the-train.html" rel="alternate"></link><published>2007-05-13T00:00:00-06:00</published><updated>2007-05-13T00:00:00-06:00</updated><author><name>Matthew T. Perry</name></author><id>tag:www.perrygeo.com,2007-05-13:/back-on-the-train.html</id><summary type="html">&lt;p&gt;I'd like to have some interesting excuse as to why I haven't posted since last July. But I don't. &lt;/p&gt;
&lt;p&gt;I've since left my postion at NCEAS, started a new job at &lt;a href="http://www.geosyntec.com"&gt;Geosyntec&lt;/a&gt; and have been keeping busy with life, love and the pursuit of happiness. Oh and GIS of course …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I'd like to have some interesting excuse as to why I haven't posted since last July. But I don't. &lt;/p&gt;
&lt;p&gt;I've since left my postion at NCEAS, started a new job at &lt;a href="http://www.geosyntec.com"&gt;Geosyntec&lt;/a&gt; and have been keeping busy with life, love and the pursuit of happiness. Oh and GIS of course.&lt;/p&gt;
&lt;p&gt;Anyway, I expect to be posting on a much more regular basis from here on (unless I get distracted again ;-) ).&lt;/p&gt;</content><category term="articles"></category></entry><entry><title>Worldwind Java - Jython example</title><link href="https://www.perrygeo.com/worldwind-java-jython-example.html" rel="alternate"></link><published>2007-05-13T00:00:00-06:00</published><updated>2007-05-13T00:00:00-06:00</updated><author><name>Matthew T. Perry</name></author><id>tag:www.perrygeo.com,2007-05-13:/worldwind-java-jython-example.html</id><summary type="html">&lt;p&gt;The &lt;a href="http://worldwind.arc.nasa.gov/java/index.html"&gt; worldwind java sdk &lt;/a&gt; has finally been released.  It's a neat SDK, well organized, &lt;a href="http://tleilax.chinoy.com/worldwind/articles/20070510-FirstImpressions.html"&gt;easy to bring into Eclipse&lt;/a&gt; with some good examples to start hacking away.&lt;/p&gt;
&lt;p&gt;The only problem is the examples are written in Java  ;-) . If braces make you cringe but you still want to work with all …&lt;/p&gt;</summary><content type="html">&lt;p&gt;The &lt;a href="http://worldwind.arc.nasa.gov/java/index.html"&gt; worldwind java sdk &lt;/a&gt; has finally been released.  It's a neat SDK, well organized, &lt;a href="http://tleilax.chinoy.com/worldwind/articles/20070510-FirstImpressions.html"&gt;easy to bring into Eclipse&lt;/a&gt; with some good examples to start hacking away.&lt;/p&gt;
&lt;p&gt;The only problem is the examples are written in Java  ;-) . If braces make you cringe but you still want to work with all the excellent Java libraries out there, you'll want to take a look at Jython. Taking the AWT1Up.java code and porting a subset of the functionality to Jython was surprisingly easy and yielded much more readable code in my opinion. And the ability to manipulate objects at the interactive prompt is just so sweet. &lt;/p&gt;
&lt;p&gt;&lt;a href="/assets/img/wwj_jython.jpg"&gt; &lt;img alt="" src="/assets/img/wwj_jython_thumb.jpg"&gt; &lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="http://perrygeo.googlecode.com/svn/trunk/gis-bin/wwj_demo.py"&gt; View the Source Code &lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Setup is not too terrible:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Get a Java JDK (I'm using sun java 6) &lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Download and install Jython 2.2b2 &lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Download and unzip the worldwind java sdk (ex: /opt/wwj )&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Set your LD_LIBRARY_PATH variable to /opt/wwj&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Set your CLASSPATH variable to /opt/wwj/worldwind.jar&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Run &lt;code&gt;jython wwj_demo.py&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;One thing that is a bit disappointing with the WorldWind SDK in general is the lack of support for rendering common formats. Maybe I missed something but I couldn't get gpx or georss feeds working properly.  It is version 0.2 so I expect support for GeoRSS and GPX to improve and for GML, KML, GeoJSON, Shapefiles, Rasters, WMS, etc to be included eventually.&lt;/p&gt;
&lt;p&gt;Anyone else out there started playing with Jython / Worldwind yet?&lt;/p&gt;</content><category term="articles"></category></entry><entry><title>The reliability of web services</title><link href="https://www.perrygeo.com/the-reliability-of-web-services.html" rel="alternate"></link><published>2006-07-24T00:00:00-06:00</published><updated>2006-07-24T00:00:00-06:00</updated><author><name>Matthew T. Perry</name></author><id>tag:www.perrygeo.com,2006-07-24:/the-reliability-of-web-services.html</id><summary type="html">&lt;p&gt;A few months back I posted a link to my &lt;a href="http://www.perrygeo.net/wordpress/?p=35"&gt;ten favorite Web Mapping Services&lt;/a&gt;. The post included live links directly to the WMS servers. At first I questioned this move as locally hosted images would be far more reliable. But I thought it would be a neat experiment to …&lt;/p&gt;</summary><content type="html">&lt;p&gt;A few months back I posted a link to my &lt;a href="http://www.perrygeo.net/wordpress/?p=35"&gt;ten favorite Web Mapping Services&lt;/a&gt;. The post included live links directly to the WMS servers. At first I questioned this move as locally hosted images would be far more reliable. But I thought it would be a neat experiment to see the downtime of each site. So I checked it daily just out of curiosity...&lt;/p&gt;
&lt;p&gt;Well with today's apparent disappearance of the &lt;a href="http://wms.jpl.nasa.gov/wms.cgi?request=GetCapabilities"&gt;NASA JPL site&lt;/a&gt;, all but one of my WMS layers mentioned have been down for at least a significant portion of a day. (The only one that's been consitently up has been http://mesonet.agron.iastate.edu) .&lt;/p&gt;
&lt;p&gt;This echos back to what I was complaining about with the whole &lt;a href="http://www.perrygeo.net/wordpress/?p=43"&gt;USGS National Map debacle&lt;/a&gt;. The bottom line is that whenever we rely heavily on a web service to deliver essential data, we are risking the integrity of the end product. The chain is only as strong as it's weakest link and, unfortunately as the USGS and NASA have shown, those links can and will fail completely from time to time.&lt;/p&gt;</content><category term="articles"></category></entry><entry><title>Converting Shapefiles (and more) to KML</title><link href="https://www.perrygeo.com/converting-shapefiles-and-more-to-kml.html" rel="alternate"></link><published>2006-07-14T00:00:00-06:00</published><updated>2006-07-14T00:00:00-06:00</updated><author><name>Matthew T. Perry</name></author><id>tag:www.perrygeo.com,2006-07-14:/converting-shapefiles-and-more-to-kml.html</id><summary type="html">&lt;p&gt;A while back I wrote about converting &lt;a href="http://www.perrygeo.net/wordpress/?p=3"&gt;KML files into a shapefile&lt;/a&gt; for use with GIS apps other than GoogleEarth. I got a ton of emails and site traffic from people looking to go the opposite direction; getting their GIS data into KML. &lt;/p&gt;
&lt;p&gt;There are, of course, a couple of …&lt;/p&gt;</summary><content type="html">&lt;p&gt;A while back I wrote about converting &lt;a href="http://www.perrygeo.net/wordpress/?p=3"&gt;KML files into a shapefile&lt;/a&gt; for use with GIS apps other than GoogleEarth. I got a ton of emails and site traffic from people looking to go the opposite direction; getting their GIS data into KML. &lt;/p&gt;
&lt;p&gt;There are, of course, a couple of utilities already implemented: ArcMap-based extensions including &lt;a href="http://arcscripts.esri.com/details.asp?dbid=14344"&gt;KML Home Companion&lt;/a&gt; and &lt;a href="http://www.arc2earth.com/"&gt;Arc2Earth&lt;/a&gt;, a nice MapWindow app called &lt;a href="http://interactiveearth.blogspot.com/2006/06/download-shape2earth-beta-2.html"&gt; Shape2Earth&lt;/a&gt;, and the open source WMS &lt;a href="http://docs.codehaus.org/display/GEOS/Home"&gt;Geoserver&lt;/a&gt; all support KML output. &lt;/p&gt;
&lt;p&gt;Not to be left behind, GDAL/OGR now supports KML output.  Oddly enough it does not yet read KML. But hand it any &lt;a href="http://ogr.maptools.org/ogr_formats.html"&gt;OGR-readable vector dataset&lt;/a&gt; and it can be converted into KML. It currently doesn't offer as much control over the output as the above options but is quicker to implement, works with a wide variety of input formats and can be easily scripted.&lt;/p&gt;
&lt;p&gt;This functionality is in CVS only at the moment but should be included in the next release. If you can't wait and don't feel like compiling from cvs source, try the 1.0.5 version of &lt;a href="http://fwtools.maptools.org/"&gt;FWTools&lt;/a&gt; (for Windows and Linux).&lt;/p&gt;
&lt;p&gt;The conversion process is pretty straightforward. For example, the following will convert a shapefile (sbpoints.shp) to KML (mypoints.kml). &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;ogr2ogr -f KML mypoints.kml sbpoints.shp sbpoints
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The KML format flys in the face of the GIS mantra stating that content should be seperate from styling. Since styling information is purposefully absent from most standard vector formats, it makes for pretty bland KML output. The attributes just get dumped out into one big text block and there is no classification or styling control.
&lt;img alt="" src="/assets/img/ogrkml.jpg"&gt;&lt;/p&gt;
&lt;p&gt;But in terms of getting your data into Google Earth quickly (esp. point data), the OGR method looks promising.&lt;/p&gt;</content><category term="articles"></category></entry><entry><title>Wardriving with Ubuntu Linux and Google Earth</title><link href="https://www.perrygeo.com/wardriving-with-ubuntu-linux-and-google-earth.html" rel="alternate"></link><published>2006-07-03T00:00:00-06:00</published><updated>2006-07-03T00:00:00-06:00</updated><author><name>Matthew T. Perry</name></author><id>tag:www.perrygeo.com,2006-07-03:/wardriving-with-ubuntu-linux-and-google-earth.html</id><summary type="html">&lt;p&gt;Wardriving is fun. Going around the neighborhood and mapping all the wireless networks may be nothing more than a geeky hobby but it can sure teach you alot. And viewing the results in Google Earth is icing on the cake.&lt;/p&gt;
&lt;p&gt;I've used NetStumbler on windows and this works great but …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Wardriving is fun. Going around the neighborhood and mapping all the wireless networks may be nothing more than a geeky hobby but it can sure teach you alot. And viewing the results in Google Earth is icing on the cake.&lt;/p&gt;
&lt;p&gt;I've used NetStumbler on windows and this works great but since my computers at home are now nearly Microsoft-free, I had to relearn the process on Linux. It breaks down into a few easy steps:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Install the &lt;strong&gt;drivers&lt;/strong&gt; for you wireless card. On my HP laptop with a Broadcom card, I followed the instructions on the &lt;a href="http://ubuntuforums.org/showthread.php?p=1071920&amp;amp;mode=linear"&gt; ubuntu forums &lt;/a&gt; which worked great with one exception: the driver link on that page doesn't have a valid md5 sum so you can download it from &lt;a href="http://forums.fedoraforum.org/forum/attachment.php?attachmentid=7759"&gt;this url&lt;/a&gt; instead&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Install &lt;strong&gt;gpsd.&lt;/strong&gt; This is the software that talks to your gps unit and is available in the ubuntu packages through apt. The one hitch is that I had to set up my Magellan GPS unit up for the correct baud rate and NMEA output. Once installed, I connected the GPS unit via a serial port, turned it on and ran _ gpsd /dev/ttyS0 _ to start the gpsd server.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Install &lt;strong&gt;kismet,&lt;/strong&gt; the wireless packet sniffer. The version in the ubuntu repository is not recent enough to support my Broadcom driver so I had to download the latest source and compile it with the standard _ configure, make, sudo make install &lt;em&gt;.  Then I had to edit the /usr/local/etc/kismet.conf to reflect my system configuration; I changed the _suiduser&lt;/em&gt;, &lt;em&gt;source&lt;/em&gt; and &lt;em&gt;logtemplate&lt;/em&gt; variables. Once configured, you can start it with the command &lt;em&gt;sudo kismet&lt;/em&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Now &lt;strong&gt;drive/bike/walk around&lt;/strong&gt; for a bit with your laptop and gps unit. When you're done, shutdown kismet and you'll have a bunch of fresh logfiles to work with.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The main kismet log is an xml file containing all the info on the available wireless networks including their SSID, their encryption sheme, transfer rater and their geographic position via gpsd. I worked up a small python script, &lt;a href="http://perrygeo.googlecode.com/svn/trunk/gis-bin/kismet2kml.py"&gt;kismet2kml.py&lt;/a&gt; (based on a blog entry at &lt;a href="http://www.larsen-b.com/Article/204.html"&gt;jkx@Home&lt;/a&gt;), to &lt;strong&gt;parse the logfile into a KML file&lt;/strong&gt; for use with Google Earth. It could certainly use some tweaking but it's a start. To run it, give it the kismet logfile and pipe the output to a kml file:  &lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;kismet2kml.py kismet-log-Jul-03-2006-1.xml &amp;gt; wardrive.kml&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;ol&gt;
&lt;li&gt;Now fire up &lt;strong&gt;Google Earth&lt;/strong&gt; (Linux version now available!) and load your KML file.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img alt="" src="/assets/img/kismetkml.jpg"&gt;&lt;/p&gt;
&lt;p&gt;Also, as James Fee &lt;a href="http://www.spatiallyadjusted.com/2006/07/03/help-me-think-of-a-good-mashup-to-create/"&gt;points out&lt;/a&gt;, posting your data as KML files means that the data can be integrated into a growing number of kml-ready apps including google maps (just upload the kml and point your browser to &lt;em&gt;http://maps.google.com/maps?q=http://your.server/wardrive.kml&lt;/em&gt;).  &lt;/p&gt;
&lt;p&gt;Another neat application I've found for dealing with kismet logs is the &lt;a href="http://wiki.openstreetmap.org/index.php/User:Dutch#Converting_Kismet_.gps_files_to_gpx"&gt;kismet2gpx script&lt;/a&gt; for converting the kismet gps tracklog into gpx. Since most gps units have pretty tight limitations on the length of stored tracks, logging them to your laptop with kismet could be an effective way of creating detailed tracks on very long trips.&lt;/p&gt;</content><category term="articles"></category></entry><entry><title>Mapserver Include</title><link href="https://www.perrygeo.com/mapserver-include.html" rel="alternate"></link><published>2006-06-25T00:00:00-06:00</published><updated>2006-06-25T00:00:00-06:00</updated><author><name>Matthew T. Perry</name></author><id>tag:www.perrygeo.com,2006-06-25:/mapserver-include.html</id><summary type="html">&lt;p&gt;If you mange even a small number of Mapserver sites, eventually you notice that you use a number of identical layers in multiple mapfiles. The way this is typically done is to copy and paste the LAYER definition into each mapfile. But inevitably you'll need to change the styling or …&lt;/p&gt;</summary><content type="html">&lt;p&gt;If you mange even a small number of Mapserver sites, eventually you notice that you use a number of identical layers in multiple mapfiles. The way this is typically done is to copy and paste the LAYER definition into each mapfile. But inevitably you'll need to change the styling or the data source and you have to manually go through each mapfile to sync the changes. Wouldn't it be nice to define the layer in a single file and use it in many mapfiles?&lt;/p&gt;
&lt;p&gt;While Mapserver has no concept of an "include", the C preprocessor (cpp) does. This is mentioned on the Mapserver list every time the subject of includes comes up. Still I have yet to find an actual example so I thought I'd share my notes on how I accomplish a mapserver include:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Create your mapfile as usual but leave out any LAYER definitions that you wish to share amongst mapfiles. Instead use something like :&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;h1&gt;include "landsat.layer"&lt;/h1&gt;
&lt;/blockquote&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;The C Preprocessor doesn't deal well with "#" which is the mapfile's chosen comment charachter. Instead replace with "##" to indicate a comment &lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Save this pseudo-mapfile as &lt;em&gt;mymap.template&lt;/em&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Create a file in the same directory called &lt;em&gt;landsat.layer&lt;/em&gt; with the LAYER block. &lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Run the template through the preprocessor to generate the real mapfile :&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;cpp -P -C -o mymap.map mymap.template &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The next step would be to script the preprocessing of &lt;em&gt;all&lt;/em&gt; your mapfiles so that changing a layer definition in multiple mapfiles was as simple as changing the *.layer file and running the script. &lt;/p&gt;</content><category term="articles"></category></entry><entry><title>Some thoughts on Where 2.0</title><link href="https://www.perrygeo.com/some-thoughts-on-where-20.html" rel="alternate"></link><published>2006-06-15T00:00:00-06:00</published><updated>2006-06-15T00:00:00-06:00</updated><author><name>Matthew T. Perry</name></author><id>tag:www.perrygeo.com,2006-06-15:/some-thoughts-on-where-20.html</id><summary type="html">&lt;p&gt;Oh man, it's a long drive from San Jose back to Santa Barbara! Anyways, just got back from where 2.0 and want to throw out my quick summary of the event.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;There was alot of talk about all things &lt;strong&gt;open&lt;/strong&gt;; open data, open source and open standards. There was …&lt;/li&gt;&lt;/ul&gt;</summary><content type="html">&lt;p&gt;Oh man, it's a long drive from San Jose back to Santa Barbara! Anyways, just got back from where 2.0 and want to throw out my quick summary of the event.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;There was alot of talk about all things &lt;strong&gt;open&lt;/strong&gt;; open data, open source and open standards. There was lots of buzz around the open street map project, osgeo applications like grass, ossim, gdal, mapbender, etc., and tons of discussion of WMS, WFS and other relevant standards. This is great as I think all three will be the cornerstone of the spatial industry in the near future. &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;But, as I've mentioned before, people throw the word "open" around so much that it begins to loose meaning. From alot of conversations I had, I found many people were confused about the differences. Some folks seemed to think that the osgeo foundation was a data repository for open data (it may soon be! .. but not quite yet) and also that osgeo was an open standards organization trying to "compete" with the OGC. But that is what an event like this is for; to reach out and communicate, clarify and bridge the gaps between communities.&lt;/p&gt;
&lt;p&gt;Of course I had to laugh as I heard a couple dozen people refer to Google Maps as an "open source" application.... it's proprietary source code using proprietary data through a proprietary data transfer mechanism. It may be "free" as in beer but that's about the extent of it's openness.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Social Data&lt;/strong&gt;: using location technology as the basis for sharing personal experiences and social networking was a powerful theme at Where 2.0. It ran the gammut from tagging locations to writing personal travelogs to mobile location-based games to virtual worlds to mobile apps that would could differentiate stangers vs aquantainces in range of your bluetooth device. &lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Security and privacy&lt;/strong&gt;: There are implications to the web/where2.0 mindframe. Publishing your location and personal information in real time through the web and mobile devices brings up some frightening security and privacy issues. Who owns the data? What licenses are your personal data distributed under? Do you need others permission to post their photos or locations? Who decides what is acceptable and what gets taken down? How is spam dealt with? Only two speakers were brave enough to fully address these issues head on and the panel had some good discussion on these topics. Kudos to them. &lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Bringing location technology to &lt;strong&gt;the masses&lt;/strong&gt;: This was repeated by a few speakers; that in order to be successful in spatial technologies you need to bring your service to the masses. Certainly if you're trying to compete in the social networking space, this is true. But in general GIS and spatial tech has application that are far beyond the interests of the vast majority of people.. emergency management, infrastructure, environmental, real estate, etc.  &lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The mantra that spatial data and services must appeal to a wide audience is analogous to saying that family cars are the only successful type of motorized vehicle. In terms of numbers, they may be a majority. But in terms of utility, there is a reason that construction companies pay hundreds of thousands of dollars for heavy industrial machinery.. because trying to haul tons of earth and debris with a Toyota Camry just doesn't work.  Likewise there is a similar reason most municipalities don't use a Google Mashup to manage their parcel data.. it simply doesn't work. So what is appropriate for mass consumption may have little applicabilty to business/government/industry/research. And vice versa. &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Mobile Applications&lt;/strong&gt;: So much potential here and some really cool innovations in geotagging content. Really, for the first time, I got a sense that these personal devices could become a means for creating a vast database of socially relevant information. But the lack of security and privacy safegaurds along with the domination of the cellular networks and the heterogenous environment of mobile platforms, I still view most of this as pie-in-the-sky.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Some new discoveries: &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;metacarta: A text parsing engine with a public API to extract geo info from plain text! &lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;gutenkarte: An application of the above to classic works of literature.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;open layers: A javascript application with a slick UI and simple API for displaying WMS and WFS&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;open street map: A fantastic project focussing on collaborative development of a public street database &lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;mapstraction: A javascript layer on top of the 'Big 3' Mapping APIs that allows yoiu to switch seamlessly between the service providers.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Google Earth &amp;amp; Sketchup: GE for linux!!! Wooo-hooo!! There was also a sweet demo of creating 3D drawings in Sketchup and placing them in GE. Very slick.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Google Maps: Now with kml support! Just try http://maps.google.com/?q=http://path.to.your.kml &lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Mapguide: I am embarrased to say I have never tried out Autodesk's open source offering but the demo was sweet.. a very high powered GIS for a web app. And the Autodesk folks were about the nicest group of guys you could meet.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;ArcGIS/Server 9.2: Author a map in ArcMap. Save as .mxd. Drop into web server. Instant kml and wms server! &lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;And while not new to me, there were alot of good overviews of some of my favorite software packages like OSSIM, GRASS, GDAL, Geoserver and World Wind (Java version coming this fall!!). &lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Finally, the prize for most interesting talk goes to Chris Spurgeon who spoke about the best geohacks  of the last 3000 years. Long before computers, Chris showed how Eratosthenes measured the diameter of the earth, how the Polypenesian's used the stars as an advanced navigation system, how the  post-renaissance world _re_discovered stars as a the key to navigation. And in more recent times he showed how Harry Beck reinvented the cartography of transportation with the London subway maps and how the VOR transmitters created highways in the featureless sky. This presentation really put current innovations in location technologies into perspective.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;OK sorry about the lack of links but it's too late in the evening for that. Hope you enjoyed my rundown and I'm sure I'll have more to say after I get some sleep!&lt;/p&gt;</content><category term="articles"></category></entry><entry><title>Animating the Blue Marble</title><link href="https://www.perrygeo.com/animating-the-blue-marble.html" rel="alternate"></link><published>2006-06-09T00:00:00-06:00</published><updated>2006-06-09T00:00:00-06:00</updated><author><name>Matthew T. Perry</name></author><id>tag:www.perrygeo.com,2006-06-09:/animating-the-blue-marble.html</id><summary type="html">&lt;p&gt;A while back I posted my technique for creating an &lt;a href="http://www.perrygeo.net/wordpress/?p=39"&gt;animated gif&lt;/a&gt; out of a time series of maps. While this may have been the pinnacle of web animation circa 1997, the animated gif just didn't quite seem hip enough for this day and age.&lt;/p&gt;
&lt;p&gt;Today I found a more …&lt;/p&gt;</summary><content type="html">&lt;p&gt;A while back I posted my technique for creating an &lt;a href="http://www.perrygeo.net/wordpress/?p=39"&gt;animated gif&lt;/a&gt; out of a time series of maps. While this may have been the pinnacle of web animation circa 1997, the animated gif just didn't quite seem hip enough for this day and age.&lt;/p&gt;
&lt;p&gt;Today I found a more modern example. This &lt;a href="http://worldkit.org/wmstimenav/"&gt;WorldKit interface&lt;/a&gt;, built with Flash, shows the seasonal progression of snow and land cover changes courtesy of the next generation Blue marble images. Complete with time slider, image fading and full animation controls, this interface really shines at providing an interactive experience rather than a passive visual display. &lt;/p&gt;</content><category term="articles"></category></entry><entry><title>HostGIS Linux 3.6 Released</title><link href="https://www.perrygeo.com/hostgis-linux-36-released.html" rel="alternate"></link><published>2006-06-03T00:00:00-06:00</published><updated>2006-06-03T00:00:00-06:00</updated><author><name>Matthew T. Perry</name></author><id>tag:www.perrygeo.com,2006-06-03:/hostgis-linux-36-released.html</id><summary type="html">&lt;p&gt;Though probably not as big of a news item as this week's &lt;a href="http://www.ubuntu.com/news/606released"&gt;release of Ubuntu Dapper&lt;/a&gt;, there's another Linux release that might be of interest to us GIS folk:&lt;/p&gt;
&lt;p&gt;Built off of a &lt;a href="http://www.slackware.com/"&gt;Slackware&lt;/a&gt; base (one of the oldest, most stable linux distros), &lt;a href="http://www.hostgis.com/linux/"&gt;HostGIS Linux&lt;/a&gt; aims to be a "minimal …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Though probably not as big of a news item as this week's &lt;a href="http://www.ubuntu.com/news/606released"&gt;release of Ubuntu Dapper&lt;/a&gt;, there's another Linux release that might be of interest to us GIS folk:&lt;/p&gt;
&lt;p&gt;Built off of a &lt;a href="http://www.slackware.com/"&gt;Slackware&lt;/a&gt; base (one of the oldest, most stable linux distros), &lt;a href="http://www.hostgis.com/linux/"&gt;HostGIS Linux&lt;/a&gt; aims to be a "minimal yet complete" distribution specifically built with GIS in mind. It is first and foremost a server platform; it does not include any window system at all. If you're looking for desktop GIS applications out-of-box, it might not be the best for you. &lt;/p&gt;
&lt;p&gt;But for a GIS server, it comes with most of the open source stack preinstalled and configured. This latest release has &lt;a href="http://www.hostgis.com/linux/manual/changes.html"&gt;a few changes&lt;/a&gt; and version upgrades for most of the components.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;PHP, Python and Perl Mapscript &lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;GDAL/OGR with PHP, Python and perl bindings &lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Postgresql 8.1 with PostGIS 1.1 &lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;drivers for many extra formats including jpeg2000 and ecw &lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Apache web server with Mapserver CGI &lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The primary motivation for creating HGL was to speed up the installation of new gis-enabled servers. Gregor Mosheh, the head programmer for HostGIS, has done an excellent job pretty much single-handedly putting this together. ( In full disclosure, I do consulting work for HostGIS, though I wasn't really involved in the creation of HostGIS Linux. )&lt;/p&gt;
&lt;p&gt;The setup is your standard text-based install and is a piece of cake if you've ever installed Linux before. When you're through, you have the good ole' black and white text console staring at you. Not very interesting... But the really satisfying part is to fire up a web browser after the install and be able to point it to a working webGIS application. Anyone who has spent the time to set up the mapserver stack and its seemingly infinite dependencies can appreciate the amount of work this saves! &lt;/p&gt;
&lt;p&gt;If you're not into learning a new distro, there is always the &lt;a href="http://www.maptools.org/fgs/"&gt;FGS&lt;/a&gt; linux installer which will set up a similar software stack on pretty much any linux.&lt;/p&gt;
&lt;p&gt;And for Desktop GIS, many linux distros have a selection of GIS apps in their package repositories (You'll want to certainly grab GRASS, GDAL and QGIS) . &lt;a href="http://fwtools.maptools.org/"&gt;FWTools&lt;/a&gt; can be a good option on both Linux and Windows to get you up and running quickly. Finally there are a number of other more desktop-oriented distros for GIS including &lt;a href="http://www.sourcepole.com/gis-knoppix/"&gt; Knoppix GIS&lt;/a&gt; and &lt;a href="http://www.geolivre.org.br/modules/news/"&gt;GeoLivre&lt;/a&gt;, both of which run as a live-cd so you can check it out before you install.&lt;/p&gt;
&lt;p&gt;Anyways, back to sum up HostGIS Linux: &lt;/p&gt;
&lt;p&gt;If you need to set up a GIS server with minimal fuss and you have some experience with Linux, you might like to try it out. It will save lots of time. &lt;/p&gt;
&lt;p&gt;If you're a GIS user who needs a graphic windows environment to do GIS work on the Desktop, HostGIS Linux will not really make you happy out-of-the-box. Of course, since HGL is slackware based, you &lt;em&gt;can&lt;/em&gt; use the slackware package management system to build an impressive Desktop system. But if you don't need to run a server or really care about having the latest versions, Ubuntu comes with a solid desktop environment and packages for alot of good GIS apps. &lt;/p&gt;</content><category term="articles"></category></entry><entry><title>More on Mapnik WMS</title><link href="https://www.perrygeo.com/more-on-mapnik-wms.html" rel="alternate"></link><published>2006-05-18T00:00:00-06:00</published><updated>2006-05-18T00:00:00-06:00</updated><author><name>Matthew T. Perry</name></author><id>tag:www.perrygeo.com,2006-05-18:/more-on-mapnik-wms.html</id><summary type="html">&lt;p&gt;One of my initial complaints about the Mapnik WMS server was that it would not accept any parameters that were not in the OGC WMS spec. Some WMS clients will tag on extra parameters for various reasons and the OGC supports this in relation to vendor-specific parameters. The fix was …&lt;/p&gt;</summary><content type="html">&lt;p&gt;One of my initial complaints about the Mapnik WMS server was that it would not accept any parameters that were not in the OGC WMS spec. Some WMS clients will tag on extra parameters for various reasons and the OGC supports this in relation to vendor-specific parameters. The fix was pretty simple;in &lt;strong&gt;mapnik/ogcserver/common.py&lt;/strong&gt; you can simply comment out         &lt;/p&gt;
&lt;blockquote&gt;&lt;/blockquote&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;#for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;paramname&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;paramname&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;not&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SERVICE_PARAMS&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;requestname&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;raise&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;OGCException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Unknown request parameter &amp;quot;%s&amp;quot;.&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;paramname&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;to get the desired effect.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;There was also the question of speed and how it compared to other WMS servers such as Mapserver. Since I already had both a Mapnik and Mapserver WMS set up using the exact same data source, styled in the same fashion, it was pretty simple to write a quick python script that would smack each WMS server with a given number of back-to-back WMS GetMap requests:&lt;/p&gt;
&lt;blockquote&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt; 1&lt;/span&gt;
&lt;span class="normal"&gt; 2&lt;/span&gt;
&lt;span class="normal"&gt; 3&lt;/span&gt;
&lt;span class="normal"&gt; 4&lt;/span&gt;
&lt;span class="normal"&gt; 5&lt;/span&gt;
&lt;span class="normal"&gt; 6&lt;/span&gt;
&lt;span class="normal"&gt; 7&lt;/span&gt;
&lt;span class="normal"&gt; 8&lt;/span&gt;
&lt;span class="normal"&gt; 9&lt;/span&gt;
&lt;span class="normal"&gt;10&lt;/span&gt;
&lt;span class="normal"&gt;11&lt;/span&gt;
&lt;span class="normal"&gt;12&lt;/span&gt;
&lt;span class="normal"&gt;13&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="ch"&gt;#!/usr/bin/env python&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;urllib&lt;/span&gt;

&lt;span class="n"&gt;server&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;hits&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;server&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;mapnik&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;http://localhost/fcgi-bin/wms?VERSION=1.1.1&amp;amp;REQUEST;=GetMap&amp;amp;SERVICE;=WMS&amp;amp;LAYERS;=world_borders&amp;amp;SRS;=EPSG:4326&amp;amp;BBOX;=-4.313249999999993,20.803500000000003,59.58675000000002,52.75350000000002&amp;amp;WIDTH;=800&amp;amp;HEIGHT;=400&amp;amp;FORMAT;=image/png&amp;amp;STYLES;=&amp;amp;TRANSPARENT;=TRUE&amp;amp;UNIQUEID;=&amp;quot;&lt;/span&gt;
&lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;server&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;mapserver&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;http://localhost/cgi-bin/mapserv?map=/home/perrygeo/mapfiles/world.map&amp;amp;VERSION;=1.1.1&amp;amp;REQUEST;=GetMap&amp;amp;SERVICE;=WMS&amp;amp;LAYERS;=worldborders&amp;amp;SRS;=EPSG:4326&amp;amp;BBOX;=-4.313249999999993,20.803500000000003,59.58675000000002,52.75350000000002&amp;amp;WIDTH;=800&amp;amp;HEIGHT;=400&amp;amp;FORMAT;=image/png&amp;amp;STYLES;=&amp;amp;TRANSPARENT;=TRUE&amp;amp;UNIQUEID;=&amp;quot;&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;hits&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;urllib&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;urlretrieve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;

&lt;/blockquote&gt;
&lt;p&gt;Then just run the script from the command line, specifying the server and number of hits, and wrap it in the &lt;em&gt;time&lt;/em&gt; command. Here are the results:&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="/assets/img/manik_vs_mapserv_speed.png"&gt;&lt;/p&gt;
&lt;p&gt;Pretty close. Mapserver was just slightly faster in every case. Now this is just a preliminary test and it would be interested to see a comparison:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;With larger datasets and more complex styling including classification and text labelling&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;With data from other sources such as postgis where the connection overhead might be significant&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;With Mapserver running as a fastcgi &lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;With concurrent requests as opposed to back-to-back requests &lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Overall though, my opinion of Mapnik WMS remains high and I'd love to put it in production use in the near future. Stay tuned...&lt;/p&gt;</content><category term="articles"></category></entry><entry><title>Mapnik WMS Server</title><link href="https://www.perrygeo.com/mapnik-wms-server.html" rel="alternate"></link><published>2006-05-17T00:00:00-06:00</published><updated>2006-05-17T00:00:00-06:00</updated><author><name>Matthew T. Perry</name></author><id>tag:www.perrygeo.com,2006-05-17:/mapnik-wms-server.html</id><summary type="html">&lt;p&gt;A few months ago, &lt;a href="http://mapnik.org/"&gt; Mapnik&lt;/a&gt; came onto my radar and I was immediately impressed with the &lt;a href="http://mapnik.org/maps/"&gt;beautiful&lt;/a&gt; &lt;a href="http://static.flickr.com/35/106561736_afcdc30ddb_o.png"&gt;cartography&lt;/a&gt;. But, until recently, it was just a C++ libary with some python bindings that could be used to programmatically build nice map images from shapfiles, geotiffs or postgis layers. There were no …&lt;/p&gt;</summary><content type="html">&lt;p&gt;A few months ago, &lt;a href="http://mapnik.org/"&gt; Mapnik&lt;/a&gt; came onto my radar and I was immediately impressed with the &lt;a href="http://mapnik.org/maps/"&gt;beautiful&lt;/a&gt; &lt;a href="http://static.flickr.com/35/106561736_afcdc30ddb_o.png"&gt;cartography&lt;/a&gt;. But, until recently, it was just a C++ libary with some python bindings that could be used to programmatically build nice map images from shapfiles, geotiffs or postgis layers. There were no common interfaces such as WMS to access mapnik... until last month. Jean Francois Doyon recently added &lt;a href="http://mapnik.org/news/2006/apr/18/wms/"&gt;a prototype WMS interface&lt;/a&gt; to Mapnik. It runs as a fastcgi script under apache. It is still a bit rough around the edges but the result is well worth a little extra setup effort. &lt;/p&gt;
&lt;p&gt;I set up Mapnik as a WMS server recently and would like to share my process and results. This tutorial assumes you already have python, postgresql/postgis, proj4, python imaging library and apache2 already running. The examples are for Ubuntu Dapper Drake.. they may work well on other versions of Ubuntu and Debian but for other unixes (and certainly windows) many things may need to be tweaked.&lt;/p&gt;
&lt;p&gt;First off, we have to install the base mapnik libs. These depend on the boost python bindings and the whole compile process is very simple (if a bit slow) in Ubuntu:&lt;/p&gt;
&lt;blockquote&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;sudo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;apt&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;install&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;\
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;libboost&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;python1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mf"&gt;33.1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;libboost&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;python&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;dev&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;\
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;libboost&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;regex1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mf"&gt;33.1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;libboost&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;regex&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;dev&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;\
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;libboost&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;serialization&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;dev&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;\
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;libboost&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;signals1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mf"&gt;33.1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;libboost&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;signals&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;dev&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;\
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;libboost&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;thread1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mf"&gt;33.1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;libboost&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;thread&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;dev&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;\
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;libboost&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;program&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;options1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mf"&gt;33.1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;libboost&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;program&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;dev&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;\
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;libboost&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;filesystem1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mf"&gt;33.1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;libboost&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;filesystem&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;dev&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;\
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;libboost&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;iostreams1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mf"&gt;33.1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;libboost&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;iostreams&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;dev&lt;/span&gt;
&lt;span class="n"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;~/&lt;/span&gt;&lt;span class="n"&gt;src&lt;/span&gt;
&lt;span class="n"&gt;svn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;checkout&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;svn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="n"&gt;svn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;berlios&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;de&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;mapnik&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;trunk&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;mapnik&lt;/span&gt;
&lt;span class="n"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;mapnik&lt;/span&gt;
&lt;span class="n"&gt;python&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;scons&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;scons&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;PYTHON&lt;/span&gt;&lt;span class="o"&gt;=/&lt;/span&gt;&lt;span class="n"&gt;usr&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;bin&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;python&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;PGSQL_INCLUDES&lt;/span&gt;&lt;span class="o"&gt;=/&lt;/span&gt;&lt;span class="n"&gt;usr&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;local&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;include&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;postgresql&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;\
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;PGSQL_LIBS&lt;/span&gt;&lt;span class="o"&gt;=/&lt;/span&gt;&lt;span class="n"&gt;usr&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;local&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;postgresql&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;BOOST_INCLUDES&lt;/span&gt;&lt;span class="o"&gt;=/&lt;/span&gt;&lt;span class="n"&gt;usr&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;include&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;boost&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;BOOST_LIBS&lt;/span&gt;&lt;span class="o"&gt;=/&lt;/span&gt;&lt;span class="n"&gt;usr&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;lib&lt;/span&gt;
&lt;span class="n"&gt;sudo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;python&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;scons&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;scons&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;install&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;PYTHON&lt;/span&gt;&lt;span class="o"&gt;=/&lt;/span&gt;&lt;span class="n"&gt;usr&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;bin&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;python&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;PGSQL_INCLUDES&lt;/span&gt;&lt;span class="o"&gt;=/&lt;/span&gt;&lt;span class="n"&gt;usr&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;local&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;include&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;postgresql&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;\
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;PGSQL_LIBS&lt;/span&gt;&lt;span class="o"&gt;=/&lt;/span&gt;&lt;span class="n"&gt;usr&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;local&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;postgresql&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;BOOST_INCLUDES&lt;/span&gt;&lt;span class="o"&gt;=/&lt;/span&gt;&lt;span class="n"&gt;usr&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;include&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;boost&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;BOOST_LIBS&lt;/span&gt;&lt;span class="o"&gt;=/&lt;/span&gt;&lt;span class="n"&gt;usr&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;lib&lt;/span&gt;
&lt;span class="n"&gt;sudo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ldconfig&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;/blockquote&gt;
&lt;p&gt;Now we have to set up some additional libs in order to run the WMS:&lt;/p&gt;
&lt;blockquote&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nt"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;~/&lt;/span&gt;&lt;span class="nt"&gt;src&lt;/span&gt;
&lt;span class="nt"&gt;wget&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;http&lt;/span&gt;&lt;span class="o"&gt;://&lt;/span&gt;&lt;span class="nt"&gt;easynews&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;dl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;sourceforge&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;net&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;sourceforge&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;jonpy&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;jonpy-0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;06&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;tar&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;gz&lt;/span&gt;
&lt;span class="nt"&gt;tar&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-xzvf&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;jonpy-0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;06&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;tar&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;gz&lt;/span&gt;
&lt;span class="nt"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;jonpy-0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;06&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;
&lt;span class="nt"&gt;sudo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;python&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;setup&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;py&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;install&lt;/span&gt;









&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;copy&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;ogcserver&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;stuff&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;into&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;its&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;own&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;dir&lt;/span&gt;
&lt;span class="nt"&gt;mkdir&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;opt&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;mapnik&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;opt&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;mapnik&lt;/span&gt;
&lt;span class="nt"&gt;cp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;~/&lt;/span&gt;&lt;span class="nt"&gt;src&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;mapnik&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;utils&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;ogcserver&lt;/span&gt;&lt;span class="o"&gt;/*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;/blockquote&gt;
&lt;p&gt;Now you'll want to edit the &lt;strong&gt;ogcserver.conf&lt;/strong&gt; file and change the following lines. The &lt;em&gt;module&lt;/em&gt; is essentially the name of a python file (minus the .py extension) that we'll create later. The height and width just cutoff the maximum possible image size that can be requested.&lt;/p&gt;
&lt;blockquote&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;  &lt;span class="k"&gt;module&lt;/span&gt;=&lt;span class="n"&gt;worldMapFactory&lt;/span&gt;
  &lt;span class="n"&gt;maxheight&lt;/span&gt;=&lt;span class="mi"&gt;2048&lt;/span&gt;
  &lt;span class="n"&gt;maxwidth&lt;/span&gt;=&lt;span class="mi"&gt;2048&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;/blockquote&gt;
&lt;p&gt;Create our "map factory" module defining data sources, styles, etc.( &lt;strong&gt;worldMapFactory.py&lt;/strong&gt; ). Most of this configuration is explained in the mapnik docs and well-commented examples. One thing to note is that the shapefile must be specified &lt;em&gt;without&lt;/em&gt; the .shp extension :&lt;/p&gt;
&lt;blockquote&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;mapnik.ogcserver.WMS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;BaseWMSFactory&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;mapnik&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;WMSFactory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BaseWMSFactory&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="n"&gt;BaseWMSFactory&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="n"&gt;sty&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Style&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;rl&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Rule&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;rl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;symbols&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PolygonSymbolizer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Color&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;248&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;216&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;136&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;rl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;symbols&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LineSymbolizer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Color&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="n"&gt;sty&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rules&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rl&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;register_style&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;style1&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sty&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="n"&gt;lyr&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Layer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;world_borders&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;shape&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; \&lt;/span&gt;
&lt;span class="w"&gt;                            &lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/opt/data/world_borders/world_borders&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="n"&gt;lyr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;styles&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;style1&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;register_layer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lyr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;finalize&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;/blockquote&gt;
&lt;p&gt;Now we need to set up apache2 to handle fastcgi:&lt;/p&gt;
&lt;blockquote&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;sudo apt-get install libapache2-mod-fcgid
sudo a2enmod fcgid
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;/blockquote&gt;
&lt;p&gt;... and add some config lines to the apache config files, usually /etc/apache/httpd.conf but, in the case of this Ubuntu install, &lt;strong&gt;/etc/apache2/sites-enabled/default&lt;/strong&gt; :&lt;/p&gt;
&lt;blockquote&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;        ScriptAlias /fcgi-bin/ /usr/lib/fcgi-bin/
        &amp;lt; Directory &amp;quot;/usr/lib/fcgi-bin&amp;quot; &amp;gt;
                AllowOverride All
                Options +ExecCGI -MultiViews +SymLinksIfOwnerMatch
                Order allow,deny
                Allow from all
                SetHandler fastcgi-script
        &amp;lt; Directory&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;/blockquote&gt;
&lt;p&gt;Create the fast-cgi directory refered to by apache&lt;/p&gt;
&lt;blockquote&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;sudo mkdir /usr/lib/fcgi-bin
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;/blockquote&gt;
&lt;p&gt;Now create the actual server script as &lt;strong&gt;/usr/lib/fcgi-bin/wms&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt; 1&lt;/span&gt;
&lt;span class="normal"&gt; 2&lt;/span&gt;
&lt;span class="normal"&gt; 3&lt;/span&gt;
&lt;span class="normal"&gt; 4&lt;/span&gt;
&lt;span class="normal"&gt; 5&lt;/span&gt;
&lt;span class="normal"&gt; 6&lt;/span&gt;
&lt;span class="normal"&gt; 7&lt;/span&gt;
&lt;span class="normal"&gt; 8&lt;/span&gt;
&lt;span class="normal"&gt; 9&lt;/span&gt;
&lt;span class="normal"&gt;10&lt;/span&gt;
&lt;span class="normal"&gt;11&lt;/span&gt;
&lt;span class="normal"&gt;12&lt;/span&gt;
&lt;span class="normal"&gt;13&lt;/span&gt;
&lt;span class="normal"&gt;14&lt;/span&gt;
&lt;span class="normal"&gt;15&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="ch"&gt;#!/usr/bin/env python&lt;/span&gt;

&lt;span class="c1"&gt;# Your mapnik dir containing the map factory &lt;/span&gt;
&lt;span class="c1"&gt;# must be in the python path!&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;sys&lt;/span&gt;
&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/opt/mapnik&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;mapnik.ogcserver.cgiserver&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Handler&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;jon.fcgi&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nn"&gt;fcgi&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;WMSHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Handler&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;configpath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;/opt/mapnik/ogcserver.conf&amp;#39;&lt;/span&gt;

&lt;span class="n"&gt;fcgi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="n"&gt;fcgi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FCGI_RESPONDER&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;WMSHandler&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;

&lt;/blockquote&gt;
&lt;p&gt;Finally restart the apache server &lt;/p&gt;
&lt;blockquote&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;sudo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;etc&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;apache2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;force&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;reload&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;/blockquote&gt;
&lt;p&gt;Now you can access it with a WMS request like so:&lt;/p&gt;
&lt;blockquote&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nt"&gt;http&lt;/span&gt;&lt;span class="o"&gt;://&lt;/span&gt;&lt;span class="nt"&gt;localhost&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;fcgi-bin&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;wms&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="nt"&gt;VERSION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nt"&gt;1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;1&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nt"&gt;REQUEST&lt;/span&gt;&lt;span class="o"&gt;;=&lt;/span&gt;&lt;span class="nt"&gt;GetMap&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nt"&gt;LAYERS&lt;/span&gt;&lt;span class="o"&gt;;=&lt;/span&gt;&lt;span class="nt"&gt;world_borders&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;
&lt;span class="nt"&gt;FORMAT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;png&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nt"&gt;SRS&lt;/span&gt;&lt;span class="o"&gt;;=&lt;/span&gt;&lt;span class="nt"&gt;EPSG&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nd"&gt;4326&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nt"&gt;STYLES&lt;/span&gt;&lt;span class="o"&gt;;=&amp;amp;&lt;/span&gt;&lt;span class="nt"&gt;BBOX&lt;/span&gt;&lt;span class="o"&gt;;=&lt;/span&gt;&lt;span class="nt"&gt;-81&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;54375&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="nt"&gt;-58&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;3125&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="nt"&gt;-59&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;04375&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="nt"&gt;-47&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;0625&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;
&lt;span class="nt"&gt;EXCEPTIONS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nt"&gt;application&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;vnd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ogc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;se_inimage&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nt"&gt;width&lt;/span&gt;&lt;span class="o"&gt;;=&lt;/span&gt;&lt;span class="nt"&gt;600&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nt"&gt;height&lt;/span&gt;&lt;span class="o"&gt;;=&lt;/span&gt;&lt;span class="nt"&gt;300&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;/blockquote&gt;
&lt;p&gt;&lt;img alt="" src="/assets/img/mapnik.png"&gt;&lt;/p&gt;
&lt;p&gt;Compare the linework with a comparable WMS service with UMN Mapserver on the backend. I'll let the results speak for themselves...&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="/assets/img/mapserv.png"&gt;&lt;/p&gt;
&lt;p&gt;Even if it's map rendering is smooth, Mapnik's WMS server is still a bit rough around the edges:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;It does not support GetFeatureInfo requests&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The server has trouble with extra parameters. For instance some WMS clients like mapbuilder like to 
tag on an extra 'UNIQUEID' parameter to the URL and this causes an unnecessary error with mapnik's WMS server.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Mapnik intself does not support reprojection &lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;It only supports shapefiles, geotiffs and postgis layers.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The readme.txt file in docs/ogcserver/ directory of the recent mapnik SVN checkout has a full list of known features and caveats so refer to them for the complete story.&lt;/p&gt;
&lt;p&gt;But, all in all, I am &lt;em&gt;very&lt;/em&gt; impressed with the quality of the Mapnik WMS server. I figured that, since Mapnik's goal has been high-quality cartographic output, speed would be sacrificed but I didn't notice any significant lag; on the contrary I think it was actually about on-par with Mapserver running as a CGI. If it was any slower, I didn' t notice it immediately. But then again it was only working with a relatively small shapefile and I was the only user. I'd like to do more rigourous stress tests on the Mapnik WMS to see how it compares to Mapserver and Geoserver under varying loads with greater volumes of data.&lt;/p&gt;</content><category term="articles"></category></entry><entry><title>Educational ways to waste some time</title><link href="https://www.perrygeo.com/educational-ways-to-waste-some-time.html" rel="alternate"></link><published>2006-05-12T00:00:00-06:00</published><updated>2006-05-12T00:00:00-06:00</updated><author><name>Matthew T. Perry</name></author><id>tag:www.perrygeo.com,2006-05-12:/educational-ways-to-waste-some-time.html</id><summary type="html">&lt;p&gt;It's always great to find fun internet-based games that actually challenge you in "real world" skills.  (And no, working on your wizard's Ether Flame spell in EverQuest is NOT a real world skill). After all, if you going to waste some time, it might as well be educational, right. Can …&lt;/p&gt;</summary><content type="html">&lt;p&gt;It's always great to find fun internet-based games that actually challenge you in "real world" skills.  (And no, working on your wizard's Ether Flame spell in EverQuest is NOT a real world skill). After all, if you going to waste some time, it might as well be educational, right. Can you tell that my mother is a school teacher?  Happy Mothers day!&lt;/p&gt;
&lt;p&gt;Anyways, these might be old news to some folks but I've found two fun games that will keep your brain fresh.&lt;/p&gt;
&lt;p&gt;First, there is &lt;a href="http://geosense.net"&gt;GeoSense&lt;/a&gt;. This is a fanstastic interactive game that pits users one-on-one in a timed geography quiz. You're given a city and country and you have 10 seconds to click the map. The player with the best combination of speed and accuracy wins. Given &lt;a href="http://news.nationalgeographic.com/news/2006/05/0502_060502_geography.html"&gt;American youth's horrible knowledge of geography&lt;/a&gt;, this site could be really helpful.  I would recommend it to children of all ages if it weren't for the chatroom being infested with pubescent teen sex fiends. Just go use use myspace or something...&lt;/p&gt;
&lt;p&gt;Secondly, for you Python programmers out there, there is the &lt;a href="http://www.pythonchallenge.com/"&gt;Python Challenge&lt;/a&gt;, a surprisingly challenging and mind-boggling course of puzzles that can be solved with Python. Actually some people have solved them with UNIX shell commands, perl or ruby, but many of the hints are python specific. They require a good dose of logic, persistence, knowledge of python libraries and a knack for finding patterns. Basically your goal is, given a minimal set of hints to find and process the data that will lead you to the next URL. I'm on level 9 right now and, well, I'm not going to admit to anyone how long it took to get there. Addictively challenging...&lt;/p&gt;
&lt;p&gt;Thats it for now. Have fun.&lt;/p&gt;</content><category term="articles"></category></entry><entry><title>The impact of urban areas on CO2 emmissions</title><link href="https://www.perrygeo.com/the-impact-of-urban-areas-on-co2-emmissions.html" rel="alternate"></link><published>2006-05-06T00:00:00-06:00</published><updated>2006-05-06T00:00:00-06:00</updated><author><name>Matthew T. Perry</name></author><id>tag:www.perrygeo.com,2006-05-06:/the-impact-of-urban-areas-on-co2-emmissions.html</id><summary type="html">&lt;p&gt;Increases in atmoshperic carbon dioxide (CO2) due to vehicle emmisions are considered one of the most important human-induced factors of climate change. Conventional wisdom would say that urban areas, with their huge populations, dense road networks and congested freeways, are the biggest offenders. This is true to some extent. But …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Increases in atmoshperic carbon dioxide (CO2) due to vehicle emmisions are considered one of the most important human-induced factors of climate change. Conventional wisdom would say that urban areas, with their huge populations, dense road networks and congested freeways, are the biggest offenders. This is true to some extent. But, viewed from a different perspective, the &lt;em&gt;per-capita&lt;/em&gt; CO2 emmissions for these urban areas can be considerably less than surrounding rural and suburban areas.&lt;/p&gt;
&lt;p&gt;Travelmatter.org has posted &lt;a href="http://www.travelmatters.org/maps/regional/"&gt; a series of maps&lt;/a&gt; comparing these two conflicting views. Here's a sample from Chicago that demonstrates the sharp dichotomy; both entirely accurate but different ways of analyzing the same data:&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="/assets/img/co2-map-chi-med.gif"&gt;&lt;/p&gt;
&lt;p&gt;In every case, the &lt;em&gt;total&lt;/em&gt; CO2 emmissions are much greater in dense urban areas. But, &lt;em&gt;per-capita&lt;/em&gt;, the urban areas have much lower emmissions, sometimes dramatically lower. This second view indicates, as &lt;a href="http://www.worldchanging.com/archives/004390.html"&gt; WorldChanging &lt;/a&gt; points out, that living in denser neighborhoods can reduce your climate impact. It makes sense that living closer to the places you need to go on a daily basis and having more access to public transportation would reduce the emmissions impact. Maybe cities are "greener" than most of us percieve them to be? &lt;/p&gt;</content><category term="articles"></category></entry><entry><title>USGS Seamless is back</title><link href="https://www.perrygeo.com/usgs-seamless-is-back.html" rel="alternate"></link><published>2006-05-05T00:00:00-06:00</published><updated>2006-05-05T00:00:00-06:00</updated><author><name>Matthew T. Perry</name></author><id>tag:www.perrygeo.com,2006-05-05:/usgs-seamless-is-back.html</id><summary type="html">&lt;p&gt;Two weeks after I first noticed something had gone awry with the USGS Seamless site, they appear to have fixed their server issues. As of this morning, the interactive &lt;a href="http://seamless.usgs.gov/website/seamless/viewer.php"&gt;data viewer and download interface&lt;/a&gt; is fully functionaly as far as I can tell. &lt;/p&gt;
&lt;p&gt;Now be gentle on their server. Rumour …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Two weeks after I first noticed something had gone awry with the USGS Seamless site, they appear to have fixed their server issues. As of this morning, the interactive &lt;a href="http://seamless.usgs.gov/website/seamless/viewer.php"&gt;data viewer and download interface&lt;/a&gt; is fully functionaly as far as I can tell. &lt;/p&gt;
&lt;p&gt;Now be gentle on their server. Rumour has it, if you download more than 3 DEMs at a time, the server might go down for another 2 weeks! Just kidding... everything seems to be working fine. Download away....&lt;/p&gt;</content><category term="articles"></category></entry><entry><title>What’s going on with seamless.usgs.gov ?</title><link href="https://www.perrygeo.com/whats-going-on-with-seamlessusgsgov.html" rel="alternate"></link><published>2006-04-25T00:00:00-06:00</published><updated>2006-04-25T00:00:00-06:00</updated><author><name>Matthew T. Perry</name></author><id>tag:www.perrygeo.com,2006-04-25:/whats-going-on-with-seamlessusgsgov.html</id><summary type="html">&lt;p&gt;Since April 21, I have not been able to view or extract any data from the USGS Seamless site, ostensibly the central distribution center for the US National Map. The site has been changing rapidly from day to day ever since and it seems that changes are underway so at …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Since April 21, I have not been able to view or extract any data from the USGS Seamless site, ostensibly the central distribution center for the US National Map. The site has been changing rapidly from day to day ever since and it seems that changes are underway so at least we know someone is working on it.. or hacking it to pieces. The last day or two they appear to have given up and are just redirecting people to gisdata.usgs.gov which, of course, has no mention of the outage on the home page.&lt;/p&gt;
&lt;p&gt;When I develop an internet application, even if it's only used by a few people, I usually seperate the development version from the stable, live version to minimize any downtime. And if you absolutely can't keep the app running, at least put a big banner on the page indicating that the system is down so people (like me) don't waste half an hour trying to figure out what they're doing wrong.  Is this too much to ask of the USGS? They are supposed to be the official portal for accessing our nation's spatial data, right? And we're not talking about a small server hiccup here, it has been down since at least April 21st with no public indication that problems are occuring on the site. &lt;/p&gt;
&lt;p&gt;I just recieved an email this morning from the USGS web mapping admin. The emphasis is mine:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;We apologize for any issues you may have experienced lately. The Seamless server, and all related map services will be unavailable for at least the next few days. During this time, &lt;strong&gt;the sites may still appear to be functioning.&lt;/strong&gt; Some may ask for a password, and others may not show up at all. Normally our status messages are posted at http://seamless.usgs.gov. However, since this server has been affected by this outage, users are being re-directed to http://gisdata.usgs.net. We are in the process of posting a message here as well, which you will be able to monitor for any updates. We are estimating that the site will be available again by &lt;strong&gt;Monday May 1st 2006.&lt;/strong&gt; Our team is working diligently to have this service available as soon as possible. We appreciate you patience during this time. &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I really shouldn't be surprised that a government agency botched it so badly; that seems to be the norm here in the US.  But I've really come to rely on the seamless site for alot of data and it seems that 10 days of downtime for the &lt;em&gt;sole&lt;/em&gt; distributor of our seamless national spatial data archive is a bit... amateur. &lt;/p&gt;</content><category term="articles"></category></entry><entry><title>The distinction between open source and open standards</title><link href="https://www.perrygeo.com/the-distinction-between-open-source-and-open-standards.html" rel="alternate"></link><published>2006-04-23T00:00:00-06:00</published><updated>2006-04-23T00:00:00-06:00</updated><author><name>Matthew T. Perry</name></author><id>tag:www.perrygeo.com,2006-04-23:/the-distinction-between-open-source-and-open-standards.html</id><summary type="html">&lt;p&gt;Time and time again I see &lt;em&gt;open source&lt;/em&gt; and &lt;em&gt;open standards&lt;/em&gt; mentioned in the &lt;a href="http://veryspatial.com/?p=802"&gt;same&lt;/a&gt; &lt;a href="http://www.ced.org/projects/ecom.shtml#open"&gt;sentence&lt;/a&gt;. While I'm a strong proponet of both, it is a bit disheartening to see how closely intertwined the two concepts are in the eyes of many GIS folks. &lt;/p&gt;
&lt;p&gt;Open source refers to &lt;em&gt;software&lt;/em&gt; distributed …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Time and time again I see &lt;em&gt;open source&lt;/em&gt; and &lt;em&gt;open standards&lt;/em&gt; mentioned in the &lt;a href="http://veryspatial.com/?p=802"&gt;same&lt;/a&gt; &lt;a href="http://www.ced.org/projects/ecom.shtml#open"&gt;sentence&lt;/a&gt;. While I'm a strong proponet of both, it is a bit disheartening to see how closely intertwined the two concepts are in the eyes of many GIS folks. &lt;/p&gt;
&lt;p&gt;Open source refers to &lt;em&gt;software&lt;/em&gt; distributed with a license that allows access to view and modify the source code. There are also some &lt;a href="http://www.opensource.org/index.php"&gt;other criteria&lt;/a&gt; but unrestricted access to the source code is the key component. &lt;/p&gt;
&lt;p&gt;Open standards refers to &lt;em&gt;software-neutral&lt;/em&gt; specifications, usually developed collaboratively,  to accomplish a technical goal. In the GIS world, this typically means &lt;a href="http://www.opengeospatial.org/specs/?page=specs"&gt;OpenGIS specifications&lt;/a&gt; for sharing data across a network (WMS/ WFS/ WCS), data formats (GML), or for working with spatial data in a relational database (Simple Features Spec for SQL).  We could arguably include pseudo-open specifications for data such as &lt;a href="http://www.esri.com/library/whitepapers/pdfs/shapefile.pdf"&gt;shapfiles&lt;/a&gt; and &lt;a href="http://earth.google.com/kml/kml_intro.html"&gt;KML&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Open source applications do not always conform to open standards. Standards-compliant software does not necessarily have to be open source. So why are the two often mentioned in the same breath as though they were synonymous? Perhaps open source software is perceived as being "ahead" of other types of software in terms of adoption of standards; and maybe that's true. But there are many proprietary software companies that have devoted alot of effort towards making their software communicate via open standards and their efforts should not go unnoticed (&lt;a href="http://www.esri.com/software/standards/ogc-support.html"&gt;ESRI&lt;/a&gt; and &lt;a href="http://www.cadcorp.com/"&gt;Cadcorp&lt;/a&gt; just to name the two I'm familiar with). &lt;/p&gt;
&lt;p&gt;The promise of open standards is that anyone can develop and use compliant applications that can easily interoperate regardless of the chosen software package. While that promise is far from being fully realized, associating open standards with a particular type of software will not get us any closer.  &lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Update&lt;/strong&gt;: Or maybe we &lt;em&gt;are&lt;/em&gt; getting close... check out &lt;a href="http://geospatial.blogs.com/geospatial/2006/04/interoperabilit.html"&gt;Geoff Ziess' post&lt;/a&gt; on the OGC interoperability demonstration in Tampa. Ten vendors interoperating and sharing data in real time... this is what it's all about.&lt;/p&gt;</content><category term="articles"></category></entry></feed>