LGV_TZ_Lookup 1.4.2
Time zone lookup for PHP applications and HTTP clients
Loading...
Searching...
No Matches
demo.php
Go to the documentation of this file.
1<?php
2/***************************************************************************************************************************/
3/**
4 © Copyright 2023-2026, [Little Green Viper Software Development LLC](https://littlegreenviper.com)
5
6 LICENSE:
7
8 MIT License
9
10 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation
11 files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy,
12 modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
13 Software is furnished to do so, subject to the following conditions:
14
15 The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
16
17 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
18 OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19 IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
20 CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
22 [Little Green Viper Software Development LLC](https://littlegreenviper.com)
23*/
24/***************************************************************************************************************************/
25/**
26 \file
27 \brief These are the fast checks for the demo's reference geometry and cleanup guards.
28
29 Run composer test:demo after installing development dependencies. Small, known geometries check holes, ocean
30 fallback, boundary offsets, and decoding-block transitions. Cleanup checks invoke a separate worker with test
31 records that are refused before any database operation. No download or running MySQL server is required.
32*/
33declare(strict_types=1);
34
35require dirname(__DIR__).'/vendor/autoload.php';
36require dirname(__DIR__).'/demo/SystemTests.php';
37
38/***************************************************************************************************************************/
39/** \brief This counts successful assertions in the fast demo checks. */
41/***************************************************************************************************************************/
42/**
43 \brief This compares one test result with its expected value.
44 \throws RuntimeException if the values differ, including a description of the failed check.
45*/
46function demoCheck($expected, ///< The independently specified value expected from the check.
47 $actual, ///< The value returned by the operation under test.
48 string $label ///< The description included in a failure message.
49 ): void {
50 global $checks;
51 ++$checks;
52 if ($expected !== $actual) {
53 throw new RuntimeException($label.': expected '.var_export($expected, true).', got '.var_export($actual, true));
54 }
55}
56/***************************************************************************************************************************/
57/**
58 \brief This constructs the decoded Feature fields needed by the reference collector.
59 \returns: A Polygon geometry and its properties.tzid value.
60*/
61function demoFeature(string $zone, ///< The zone label for the test geometry.
62 array $rings ///< The exterior ring, followed by any hole rings.
63 ): array {
64 return ['properties' => ['tzid' => $zone], 'geometry' => ['type' => 'Polygon', 'coordinates' => $rings]];
65}
66
68$outer = [[0, 0], [10, 0], [10, 10], [0, 10], [0, 0]];
69$hole = [[3, 3], [7, 3], [7, 7], [3, 7], [3, 3]];
70$source->observe(demoFeature('Zone/Outer', [$outer, $hole]));
71$source->observe(demoFeature('Zone/Inner', [$hole]));
72$source->observe(demoFeature('Etc/GMT', [[[-10, -10], [20, -10], [20, 20], [-10, 20], [-10, -10]]]));
73demoCheck(['Zone/Outer'], $source->zonesAt(1, 1), 'Reference interior');
74demoCheck(['Zone/Inner'], $source->zonesAt(5, 5), 'Reference honors holes and enclaves');
75demoCheck(['Etc/GMT'], $source->zonesAt(-5, -5), 'Reference ocean fallback');
76demoCheck([], $source->zonesAt(30, 30), 'Reference outside coverage');
77demoCheck(15, $source->vertices, 'Expected stored outer-ring vertices');
78demoCheck(240, $source->polygonBytes, 'Expected stored polygon size');
79demoCheck(hash('sha256', pack('d*', 0, 0, 10, 0, 10, 10, 0, 10, 0, 0)), $source->shapes[1]['sha256'], 'Reference storage hash');
80demoCheck(['east' => 10, 'west' => 0, 'north' => 10, 'south' => 0], $source->shapes[1]['bounds'], 'Source bounding box');
81
82$cases = $source->casesForLargest(1);
83demoCheck(true, count($cases) >= 8, 'Both sides of a boundary are sampled');
85foreach ($cases as $case) {
86 if (($case['pair'] ?? '') === '1:0') { $firstEdge[$case['side']] = $case; }
87}
88demoCheck(['Etc/GMT'], $firstEdge[-1]['expected'], 'South side of the square falls in the ocean');
89demoCheck(['Zone/Outer'], $firstEdge[1]['expected'], 'North side of the square falls inside the zone');
90demoCheck(true, abs($firstEdge[-1]['params']['lat'] * 111.32 + 5) < 1e-8, 'Outside probe is five kilometers from the edge');
91demoCheck(true, abs($firstEdge[1]['params']['lat'] * 111.32 - 5) < 1e-8, 'Inside probe is five kilometers from the edge');
92// Analytic circle tests verify the independent ray-casting oracle across decoding-block boundaries.
94$vertices = 2053;
95for ($i = 0; $i < $vertices; ++$i) {
96 $angle = 2 * M_PI * $i / $vertices;
97 $circle .= pack('d2', 5 * cos($angle), 5 * sin($angle));
98}
99foreach ([0, 1022, 1023, 1024, 2047, 2048, 2052] as $index) {
100 $angle = 2 * M_PI * $index / $vertices;
101 demoCheck(true, DemoSourceGeometry::ringContains($circle, 4.9 * cos($angle), 4.9 * sin($angle)), 'Circle interior across block boundary');
102 demoCheck(false, DemoSourceGeometry::ringContains($circle, 5.1 * cos($angle), 5.1 * sin($angle)), 'Circle exterior across block boundary');
103}
104demoCheck(false, DemoSourceGeometry::ringContains('', 0, 0), 'Empty ring');
105
106// A corrupted cleanup record must never name a user's database; large downloads are still discarded first.
107$directory = sys_get_temp_dir().'/lgv-tz-demo-check-'.bin2hex(random_bytes(8));
108mkdir($directory, 0700);
109try {
110 file_put_contents($directory.'/boundaries.zip', 'temporary archive');
111 file_put_contents($directory.'/boundaries.json', 'temporary boundary data');
112 file_put_contents($directory.'/database.json', json_encode(['database' => 'production']));
113 $process = proc_open([PHP_BINARY, dirname(__DIR__).'/demo/demo.php', 'cleanup', $directory],
114 [0 => ['file', '/dev/null', 'r'], 1 => ['pipe', 'w'], 2 => ['pipe', 'w']], $pipes);
115 stream_get_contents($pipes[1]);
116 $error = stream_get_contents($pipes[2]);
117 fclose($pipes[1]);
118 fclose($pipes[2]);
119 demoCheck(1, proc_close($process), 'Refuse to drop an unrelated database');
120 demoCheck(true, str_contains($error, 'refusing to drop'), 'Cleanup refusal explains its reason');
121 demoCheck(false, is_file($directory.'/boundaries.zip'), 'Archive removed on failed cleanup');
122 demoCheck(false, is_file($directory.'/boundaries.json'), 'GeoJSON removed on failed cleanup');
123 demoCheck(true, is_file($directory.'/database.json'), 'Cleanup record retained for investigation');
124
125 file_put_contents($directory.'/boundaries.zip', 'temporary archive');
126 file_put_contents($directory.'/boundaries.json', 'temporary boundary data');
127 file_put_contents($directory.'/database.json', json_encode(['database' => 'lgv_tz_demo_'.str_repeat('a', 24)]));
128 $environment = getenv();
129 $environment['LGV_TZ_DEMO_PORT'] = '0';
130 $process = proc_open([PHP_BINARY, dirname(__DIR__).'/demo/demo.php', 'cleanup', $directory],
131 [0 => ['file', '/dev/null', 'r'], 1 => ['pipe', 'w'], 2 => ['pipe', 'w']], $pipes, null, $environment);
132 stream_get_contents($pipes[1]);
133 $error = stream_get_contents($pipes[2]);
134 fclose($pipes[1]);
135 fclose($pipes[2]);
136 demoCheck(1, proc_close($process), 'Database cleanup error has a failing status');
137 demoCheck(true, str_contains($error, 'LGV_TZ_DEMO_PORT'), 'Database configuration error is reported');
138 demoCheck(false, is_file($directory.'/boundaries.zip'), 'Archive removed despite database error');
139 demoCheck(false, is_file($directory.'/boundaries.json'), 'GeoJSON removed despite database error');
140 demoCheck(true, is_file($directory.'/database.json'), 'Recovery record survives database error');
141} finally {
142 foreach (glob($directory.'/*') as $file) { unlink($file); }
143 rmdir($directory);
144}
145echo 'Passed '.$checks." demo reference and cleanup checks.\n";
This retains a compact reference copy of each polygon in the decoded GeoJSON.
static ringContains(string $ring, float $lng, float $lat)
This checks a point against one reference ring, using horizontal ray casting.
if(!is_file($autoload)) $checks
Definition composer.php:11
if(PHP_VERSION_ID< 80100) $rings
$directory
Definition demo.php:107
$process
Definition demo.php:113
$outer
Definition demo.php:68
$hole
Definition demo.php:69
$cases
Definition demo.php:82
$vertices
Definition demo.php:94
$source
Definition demo.php:67
$firstEdge
Definition demo.php:84
$environment
Definition demo.php:128
demoCheck($expected, $actual, string $label)
This compares one test result with its expected value.
Definition demo.php:46
demoFeature(string $zone, array $rings)
This constructs the decoded Feature fields needed by the reference collector.
Definition demo.php:61
$circle
Definition demo.php:93
$error
Definition demo.php:116