Skip to content

Instantly share code, notes, and snippets.

@derickson
Created January 10, 2012 05:33
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save derickson/1587218 to your computer and use it in GitHub Desktop.
Save derickson/1587218 to your computer and use it in GitHub Desktop.
XQuery Choropleth
<?xml version="1.0" encoding="UTF-8"?>
<event>
<name>Bethesda</name>
<point>38.9846520, -77.0947092</point>
</event>
(: Make sure to specify coordinates in CCW order so that KML lighting works correctly :)
declare function local:coord-from-box($box as cts:box, $alt as xs:double){
<kml:coordinates>
{
let $south := xs:string(cts:box-south($box))
let $west := xs:string(cts:box-west($box))
let $north := xs:string(cts:box-north($box))
let $east := xs:string(cts:box-east($box))
let $alt := xs:string($alt)
return
(
fn:string-join((
fn:string-join(($east,$south,$alt),","),
fn:string-join(($east,$north,$alt),","),
fn:string-join(($west,$north,$alt),","),
fn:string-join(($west,$south,$alt),","),
fn:string-join(($east,$south,$alt),",")
)," ")
)
}
</kml:coordinates>
};
let $stylemap := map:map()
let $markers :=
for $box at $x in $boxes
let $s := xs:float($box/@s)
let $w := xs:float($box/@w)
let $n := xs:float($box/@n)
let $e := xs:float($box/@e)
let $freq := xs:integer($box/@count)
return
let $_ := if($freq eq $maxfreq) then xdmp:set($maxregion, $box) else ()
let $alpha := if ($freq ge 1) then 0.5 else 0.1
let $freq-prec := xs:double(fn:substring( xs:string($freq div $maxfreq), 1 , 5))
let $color-prec := xs:double(fn:substring( xs:string((if($freq eq 0) then 1 else $freq) div $maxfreq), 1 , 5))
let $style-name := fn:concat("s",fn:replace(xs:string($freq-prec),"\.",""))
let $_ := if(map:get($stylemap,$style-name)) then () else map:put($stylemap, $style-name,
<Style id="{$style-name}" xmlns="http://www.opengis.net/kml/2.2">
<PolyStyle>
<color>{local:html-color-from-percentage($color-prec, $alpha)}</color>
<colorMode>normal</colorMode>
</PolyStyle>
</Style>
)
let $alt := (200000 + (800000 * $freq-prec)) * (xs:float($sidedist) div xs:float(9.0))
let $ctsbox := cts:box($s,$w,$n,$e)
return
<Placemark xmlns="http://www.opengis.net/kml/2.2">
<description>
<h3>{$freq} document{if($freq gt 1) then 's' else ()} in this region.</h3>
</description>
<styleUrl>#{$style-name}</styleUrl>
<Polygon xmlns="http://www.opengis.net/kml/2.2">
<extrude>1</extrude>
<altitudeMode>relativeToGround</altitudeMode>
<outerBoundaryIs>
<LinearRing>
{local:coord-from-box($ctsbox,$alt)}
</LinearRing>
</outerBoundaryIs>
</Polygon>
</Placemark>
let $boxes :=
for $box in $searchres//search:box
let $s := xs:float($box/@s)
let $w := xs:float($box/@w)
let $n := xs:float($box/@n)
let $e := xs:float($box/@e)
return
if( ($s ge $lat1) and
($n le $lat2) and
($w ge $lon1) and
($e le $lon2) ) then
let $_ := xdmp:set( $maxfreq, fn:max( ($maxfreq, xs:integer( $box/@count )) ) )
return
$box
else
(: remove this box, because it is accounting for hits outside the search area :)
()
return (
xdmp:set-response-content-type("application/vnd.google-earth.kml+xml"),
'<?xml version="1.0" encoding="UTF-8"?>',
<kml xmlns="http://www.opengis.net/kml/2.2">
<Document>
<name>KML Heatmap Global</name>
<open>1</open>
{$camera}
{
<Style id="ml">
<IconStyle>
<color>FF3122D9</color>
</IconStyle>
</Style>,
for $key in map:keys($stylemap)
return
map:get($stylemap,$key),
$markers
}
</Document>
</kml>
)
<Placemark>
<description>
<h3>4 documents in this region.</h3>
</description>
<styleUrl>#s0012</styleUrl>
<Polygon>
<extrude>1</extrude>
<altitudeMode>relativeToGround</altitudeMode>
<outerBoundaryIs>
<LinearRing>
<coordinates>-54,-36,209600 -54,-27,209600 -63,-27,209600 -63,-36,209600 -54,-36,209600</coordinates>
</LinearRing>
</outerBoundaryIs>
</Polygon>
</Placemark>
xquery version "1.0-ml" ;
(: just send everything to the KML generating script :)
"/map.xqy"
let $searchres :=
search:search(
"",
<options xmlns="http://marklogic.com/appservices/search">
<additional-query>
{cts:collection-query("event")}
</additional-query>
<constraint name="mygeo">
<geo-elem>
<heatmap s="{$lat1}" w="{$lon1}" n="{$lat2}" e="{$lon2}" latdivs="{$county}" londivs="{$countx}"/>
<facet-option>gridded</facet-option>
<element ns="" name="point"/>
</geo-elem>
</constraint>
<return-results>false</return-results>
<return-facets>true</return-facets>
</options>
)
(: attempt to make the buckets square :)
let $distx := ($lon2 - $lon1) (: cts:distance( cts:point($lat1,$lon1), cts:point($lat1, $lon2) ) :)
let $disty := ($lat2 - $lat1) (: cts:distance( cts:point($lat1,$lon1), cts:point($lat2, $lon1) ) :)
let $mindist := fn:min(($distx,$disty))
let $sidedist := $mindist div $count
let $_ := xdmp:log(text{"sidedist",$sidedist},"error")
let $countx :=
if( fn:ceiling($distx div $sidedist) castable as xs:integer ) then
xs:integer(fn:ceiling($distx div $sidedist))
else
$count * 2
let $county :=
if( fn:ceiling($disty div $sidedist) castable as xs:integer ) then
xs:integer(fn:ceiling($disty div $sidedist))
else
$count
(: variables that will track maximum values :)
let $maxfreq := 0
let $maxregion := ()
(: analytics bounds -- set to entire world :)
let $lat1 := -90
let $lat2 := 90
let $lon1 := -180
let $lon2 := 180
let $count := 80
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment