Skip to content

Instantly share code, notes, and snippets.

@Dan-Q
Created April 3, 2024 15:44
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 Dan-Q/a3d9644e41b1f34866968287598469ee to your computer and use it in GitHub Desktop.
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
<?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 . "&center=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