Created
April 3, 2024 15:44
-
-
Save Dan-Q/a3d9644e41b1f34866968287598469ee to your computer and use it in GitHub Desktop.
Draws the smallest possible convex polygon that surrounds a set of successful geocaching finds/geohashpoint expeditions. See: https://danq.me/geo-limits
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/** | |
* Given an array of points, returns the convex hull over those points. | |
*/ | |
function convex_hull_over( array $points ): array { | |
$cross = function($o, $a, $b) { | |
return ($a[0] - $o[0]) * ($b[1] - $o[1]) - ($a[1] - $o[1]) * ($b[0] - $o[0]); | |
}; | |
$pointCount = count($points); | |
sort($points); | |
if ($pointCount > 1) { | |
$n = $pointCount; | |
$k = 0; | |
$h = array(); | |
// Build lower portion of hull | |
for ($i = 0; $i < $n; ++$i) { | |
while ($k >= 2 && $cross($h[$k - 2], $h[$k - 1], $points[$i]) <= 0) | |
$k--; | |
$h[$k++] = $points[$i]; | |
} | |
// Build upper portion of hull | |
for ($i = $n - 2, $t = $k + 1; $i >= 0; $i--) { | |
while ($k >= $t && $cross($h[$k - 2], $h[$k - 1], $points[$i]) <= 0) | |
$k--; | |
$h[$k++] = $points[$i]; | |
} | |
// Remove all vertices after k as they are inside of the hull | |
if ($k > 1) { | |
// ensure self-closing - | |
$h = array_splice($h, 0, $k); | |
} | |
return $h; | |
} else if ($pointCount <= 1) { | |
return $points; | |
} else { | |
return null; | |
} | |
} | |
/** | |
* Returns an array of arrays of the lon/lat coordinates of all successful geocaching finds/geohashing expeditions. | |
*/ | |
function q23_geo_convex_hull() { | |
$coords_sql = <<<END_OF_SQL | |
SELECT DISTINCT coord_lon.meta_value lon, coord_lat.meta_value lat | |
FROM wp_posts | |
LEFT JOIN wp_postmeta expedition_result ON wp_posts.ID = expedition_result.post_id AND expedition_result.meta_key = 'checkin_type' | |
LEFT JOIN wp_postmeta coord_lat ON wp_posts.ID = coord_lat.post_id AND coord_lat.meta_key = 'checkin_latitude' | |
LEFT JOIN wp_postmeta coord_lon ON wp_posts.ID = coord_lon.post_id AND coord_lon.meta_key = 'checkin_longitude' | |
LEFT JOIN wp_term_relationships ON wp_posts.ID = wp_term_relationships.object_id | |
LEFT JOIN wp_term_taxonomy ON wp_term_relationships.term_taxonomy_id = wp_term_taxonomy.term_taxonomy_id | |
LEFT JOIN wp_terms ON wp_term_taxonomy.term_id = wp_terms.term_id | |
WHERE wp_posts.post_type = 'post' AND wp_posts.post_status = 'publish' | |
AND wp_term_taxonomy.taxonomy = 'kind' | |
AND wp_terms.slug = 'checkin' | |
AND expedition_result.meta_value IN ('Found it', 'found', 'coordinates reached', 'Attended'); | |
END_OF_SQL; | |
$coords = $GLOBALS['wpdb']->get_results($coords_sql, ARRAY_N); | |
$hull = convex_hull_over( $coords ); | |
return $hull; | |
} | |
/** | |
* Generates, caches, and renders a WebP map showing the convex hull of all successful geocaching finds/geohashing expeditions. | |
*/ | |
function q23_hull_generator(){ | |
define('GEOAPIFY_API_KEY', 'INSERT A VALID API KEY HERE!'); | |
define('CACHE_DURATION', 14 * 24 * 3600); // two weeks | |
define('MAP_SCHEMA_VERSION', 1); // Update this every time you change fixed map params e.g. marker color, to bust the cache | |
define('MARKER_COLOR', '062d4d'); | |
define('CACHE_DIR', get_stylesheet_directory() . '/cache/maps/' . MAP_SCHEMA_VERSION); | |
define('WIDTH', 640); | |
define('HEIGHT', 480); | |
define('ZOOM', 1); | |
if(!is_dir(CACHE_DIR)) mkdir(CACHE_DIR, 0777, true); | |
$tmp_file = CACHE_DIR . "/hull" . WIDTH . "x" . HEIGHT . "z" . ZOOM . ".tmp.jpg"; | |
$cache_file = CACHE_DIR . "/hull" . WIDTH . "x" . HEIGHT . "z" . ZOOM . ".webp"; | |
header('Content-type: image/webp'); | |
header('Expires: ' . gmdate("D, d M Y H:i:s", time() + CACHE_DURATION) . " GMT"); | |
header('Pragma: cache'); | |
header('Cache-control: max-age=' . CACHE_DURATION); | |
if(!file_exists($cache_file) || (time()-filemtime($cache_file) > CACHE_DURATION)) { | |
// nonexistant or out of date; redownload | |
$hull = q23_geo_convex_hull(); | |
$lons = array_map(function($coord){ return $coord[0]; }, $hull); | |
$lats = array_map(function($coord){ return $coord[1]; }, $hull); | |
$mean_lon = array_sum($lons) / count($lons); | |
$mean_lat = array_sum($lats) / count($lats); | |
$hull_points = implode( ',', array_merge(...$hull) ); | |
$url = "https://maps.geoapify.com/v1/staticmap?style=osm-bright-smooth&width=" . WIDTH . "&height=" . HEIGHT . "¢er=lonlat:$mean_lon,$mean_lat&zoom=" . ZOOM . "&apiKey=" . GEOAPIFY_API_KEY . "&geometry=polygon:$hull_points;linecolor:%23" . MARKER_COLOR . ";linewidth:2;lineopacity:0.5;fillcolor:%23" . MARKER_COLOR . ";fillopacity:0.2"; | |
file_put_contents($tmp_file, file_get_contents($url)); | |
system("convert \"$tmp_file\" \"$cache_file\""); | |
} | |
readfile($cache_file); | |
die(); | |
} | |
/** | |
* Requests for /_q23-map/hull return my convex hull map: | |
*/ | |
function maybe_run_q23_hull_generator(){ | |
if(preg_match('/^\/_q23-map\/hull$/', $_SERVER['REQUEST_URI'], $matches)) { | |
q23_hull_generator(); | |
} | |
} | |
add_action( 'init', 'maybe_run_q23_hull_generator' ); | |
?> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment