The Programming Language as Human Interface

Steven Pemberton, CWI, Amsterdam

A URL in your data


<data xmlns="">
    <url>http://tile.openstreetmap.org/10/525/336.png</url>
</data>


<output ref="url"/>

http://tile.openstreetmap.org/10/525/336.png


<output ref="url" mediatype="image/*"/>

map tile

The structure of an OSM URL




http://<site>/<zoom>/<x>/<y>.png

http://tile.openstreetmap.org/10/525/336.png

Store the parts of the URL in the data, and calculate the URL




<instance>
   <map xmlns="">
      <site>http://tile.openstreetmap.org/</site>
      <zoom>10</zoom>
      <x>525</x>
      <y>336</y>
      <url/>
   </map>
 </instance>


<bind nodeset="url"
      calculate="concat(../site, ../zoom, '/', ../x, '/', ../y, '.png')"/>

Since the parts are data, you can input them as well




<input ref="zoom"><label>zoom</label></input>

Add nudge buttons




<trigger>
   <label>→</label>
   <setvalue ev:event="DOMActivate" ref="x" value=". + 1"/>
</trigger>

Fix the zoom problem




<bind nodeset="scale" 
      calculate="power(2, 26 - ../zoom)"/>
<bind nodeset="x" 
      calculate="floor(../posx div ../scale)"/>
<bind nodeset="y" 
      calculate="floor(../posy div ../scale)"/>

Why locations bounce around as you zoom

zoom

Keeping the location in the centre

porthole

Calculating the offsets




<bind nodeset="offx" 
   calculate="0 - floor(((../posx - ../x * ../scale) div ../scale)*../tilesize)" />
<bind nodeset="offy" 
   calculate="0 - floor(((../posy - ../y * ../scale) div ../scale)*../tilesize)" />


<div style="margin-left: {offx}px; margin-top: {offy}px">...

Making the map draggable; step 1: the mouse




<mouse>
   <x/><y/><state/>
</mouse>



<action ev:event="mousemove">
   <setvalue ref="mouse/x" value="event('clientX')"/>
   <setvalue ref="mouse/y" value="event('clientY')"/>
</action>
<action ev:event="mousedown">
   <setvalue ref="mouse/state">down</setvalue>
</action>
<action ev:event="mouseup">
   <setvalue ref="mouse/state">up</setvalue>
</action>

Styling the mouse cursor




<bind nodeset="mouse/cursor"
      calculate="if(../state='up', 'pointer', 'move')"/>

<div style="cursor: {cursor}">... 

Calculating the drag


<mouse>
    <x/><y/><state/><cursor/>
    <start><x/><y/></start>
    <end><x/><y/></end>
    <move><x/><y/></move>
</mouse>



<action ev:event="mousedown">
    <setvalue ref="mouse/state">down</setvalue>
    <setvalue ref="mouse/start/x" value="event('clientX')"/>
    <setvalue ref="mouse/start/y" value="event('clientY')"/>
</action>

Dragging




<bind ref="mouse/end/x" 
    calculate="if(mouse/state = 'down', mouse/x, .)"/>
<bind ref="mouse/end/y" 
    calculate="if(mouse/state = 'down', mouse/y, .)"/>
<bind ref="mouse/move/x"
    calculate="mouse/end/x - mouse/start/x"/>
<bind ref="mouse/move/y" 
    calculate="mouse/end/y - mouse/start/y"/>

Remembering the most recent location




<lastx/><lasty/>
<bind ref="posx" 
 calculate="../lastx - ../mouse/move/x*(../scale div ../tilesize)"/>
<bind ref="posy"  
 calculate="../lasty - ../mouse/move/y*(../scale div ../tilesize)"/>



<action ev:event="mouseup">
    <setvalue ref="lastx" value="posx"/>
    <setvalue ref="lasty" value="posy"/>
    <setvalue ref="mouse/start/x" value="mouse/end/x"/>
    <setvalue ref="mouse/start/y" value="mouse/end/y"/>
</action>

Source

Adding bells and whistles

<select1 ref="site">
   <label>Map</label>
   <item>
      <label>Standard</label>
      <value>http://tile.openstreetmap.org/</value>
   </item>
   <item>
      <label>Cycle</label>
      <value>http://tile.opencyclemap.org/cycle/</value>
   </item>
   <item>
      <label>Transport</label>
      <value>http://tile2.opencyclemap.org/transport/</value>
   </item>
   ...
</select1>

150-200 lines of code

Conclusion

In around 150 lines, we have coded a complete draggable map application.

The application is largely declarative: it says WHAT should happen, now HOW to do it.

We let the computer do more of the work.

The savings are immense.