我有一个正在开发的在线应用程序,用于搜索与气候http://www.wheat-gateway.org.uk/climate_search_1.php?ord=4&cns=108,114&ctrl_r=1//522/891&ctrl=1相匹配的历史小麦采集网站。
我有收集地点(大约173,000处以及从那里收集的小麦数据)及其气候记录,并且我有一套气候记录,记录了所有土地上每15分钟间隔的一组经度/纬度点。
如您所见,目前我正在将气候数据集中的点显示为Google Maps中的热图,但我想将其转换为线,点和多边形,作为geoJSON文件以显示在Google地图中,即https://developers.google.com/maps/documentation/javascript/datalayer的一部分速度(我希望如此),部分原因是出于此目的,缩小时,热图会变形,并且在视觉上也不会在向下缩放时起作用。
我发现https://assemblysys.com/php-point-in-polygon-algorithm/看起来很不错,一旦我找到了多边形,就可以在搜索结果数组中去除点-但是首先如何找到它们(以及线和点)?有什么建议吗?
您的 安迪·福布斯
答案 0 :(得分:0)
因此,基本上,我循环遍历了可以用作W> E和N> S的Google地图中的热图的点数组,发现的每个新点都会检查是否存在孤点或具有邻居。如果有邻居,则四处走走以查看多边形或线的一部分。当找到回到起点的顶点时,检查其余的点以查看它们是否在顶点内,删除所有找到的点和顶点的点,并将顶点和孤立点放入将输出geoJSON文件的数组中。逐点移动到下一个点,直到没有一个点为止。
就时间而言,最昂贵的部分是检查点是否在多边形内。我发现https://assemblysys.com/php-point-in-polygon-algorithm/是最准确的非库函数集,可以找到大多数顶点。我尝试了各种其他方法,包括https://geophp.net/,尽管我无法按照建议在服务器上安装GEOS,但发现它的速度是“ php-in-polygon-algorithm”函数的两倍。但是我发现库phpgeos https://github.com/mjaschen/phpgeo的工作要快得多。
因此,这是热图和点(如菱形),线和多边形的结果-您可以在此区域看到热图更快。
但是在较小的区域,可以接受额外的时间http://www.wheat-gateway.org.uk/climate_search_both.php?ord=4&cns=108,114&ctrl_r=1//291/1135,2//-5/12&ctrl=1,2
所以我的解决方案是找出集合中有多少个点,然后使用热图超过3000个点。我设法根据缩放级别调整了热图中的点半径,因此演示文稿在缩小时不会产生典型的失真,例如http://www.wheat-gateway.org.uk/climate_search.php?ord=4&cns=all&ctrl_r=1//1061/1361,2//23.043/27.043&ctrl=1,2&targ=107723
最棘手的问题可能是查找是否在找到的任何多边形中都有孔,请参见此处的“环”功能。
// requires library php-geos
$lines = array();
$pts = array();
$polys = array();
$holes = array();
//array to put results in
$cnt_p = 1;
$div = 0.25;
//gap between points in grid
$motion = 8;
$points=array (
'-9.50:38.75' => '-9.50 38.75',
'-9.25:38.75' => '-9.25 38.75',
'-9.25:39.00' => '-9.25 39.00',
'-9.25:39.25' => '-9.25 39.25',
'-9.00:38.50' => '-9.00 38.50',
'-9.00:38.75' => '-9.00 38.75',
'-9.00:39.00' => '-9.00 39.00',
'-9.00:39.25' => '-9.00 39.25',
'-9.00:39.50' => '-9.00 39.50',
'-9.00:39.75' => '-9.00 39.75',
'-8.75:38.00' => '-8.75 38.00',
'-8.75:38.25' => '-8.75 38.25',
'-8.75:38.50' => '-8.75 38.50',
'-8.75:38.75' => '-8.75 38.75',
'-8.75:39.00' => '-8.75 39.00',
'-8.75:39.25' => '-8.75 39.25',
'-8.75:39.50' => '-8.75 39.50',
'-8.75:39.75' => '-8.75 39.75',
'-8.75:40.00' => '-8.75 40.00',
'-8.75:40.25' => '-8.75 40.25',
'-8.75:40.50' => '-8.75 40.50',
'-8.50:38.25' => '-8.50 38.25',
'-8.50:38.50' => '-8.50 38.50',
'-8.50:38.75' => '-8.50 38.75',
'-8.50:39.00' => '-8.50 39.00',
'-8.50:39.25' => '-8.50 39.25',
'-8.50:39.50' => '-8.50 39.50',
'-8.50:39.75' => '-8.50 39.75',
'-8.25:38.50' => '-8.25 38.50',
'-8.25:38.75' => '-8.25 38.75',
'-8.25:39.00' => '-8.25 39.00',
'-8.25:39.25' => '-8.25 39.25',
'-8.25:39.50' => '-8.25 39.50',
'-8.25:39.75' => '-8.25 39.75',
'-8.00:37.25' => '-8.00 37.25',
'-8.00:38.50' => '-8.00 38.50',
'-8.00:38.75' => '-8.00 38.75',
'-8.00:39.00' => '-8.00 39.00',
'-8.00:39.25' => '-8.00 39.25',
'-8.00:39.50' => '-8.00 39.50',
'-7.75:38.75' => '-7.75 38.75',
'-7.75:39.00' => '-7.75 39.00',
'-7.75:39.25' => '-7.75 39.25',
'-7.75:39.50' => '-7.75 39.50',
'-7.75:39.75' => '-7.75 39.75',
'-7.50:38.75' => '-7.50 38.75',
'-7.50:39.00' => '-7.50 39.00',
'-7.50:39.25' => '-7.50 39.25',
'-7.50:39.50' => '-7.50 39.50',
'-7.50:39.75' => '-7.50 39.75',
'-7.50:40.00' => '-7.50 40.00',
'-7.25:39.00' => '-7.25 39.00',
'-7.25:39.25' => '-7.25 39.25',
'-7.25:39.50' => '-7.25 39.50',
'-7.25:39.75' => '-7.25 39.75',
'-7.25:40.00' => '-7.25 40.00',
'-7.00:38.00' => '-7.00 38.00',
'-7.00:38.50' => '-7.00 38.50',
'-7.00:39.50' => '-7.00 39.50',
'-7.00:39.75' => '-7.00 39.75',
'-7.00:40.00' => '-7.00 40.00',
'-7.00:40.25' => '-7.00 40.25',
'-7.00:40.50' => '-7.00 40.50',
'-7.00:40.75' => '-7.00 40.75',
)
function scope_it() {
global $scope, $points, $re, $pts, $lines, $polys, $holes;
if ($points) {
$re = key($points);
do_res($re);
//start search picking N>S and W>E
}
}
function do_res($re) {
// start enquiry
global $points, $results;
$results = array();
array_push($results, $points[$re]);
$targ = $re;
clock($targ);
//set off search
}
function clock($targ) {
// sniff along from neigbour to neigbour to find lines and polygons
global $poss, $points, $re, $starter, $motion, $pts, $results;
get_poss($targ);
//get box of points to search for neighbours
$got = NULL;
for ($i = 0; $i <= $motion - 1; ++$i) {
$pos = $i + $starter;
$find = isset($points[$poss[$pos]]);
if ($find !== FALSE) {
$got = $poss[$pos];
array_push($results, $points[$poss[$pos]]);
$i = $motion + 1;
//got a neighbour, stop search
} elseif ($pos == $motion) {
$starter = -$i;
}
}
if ($got === NULL) {
array_push($pts, $points[$re]);
$sn2 = explode(":", $re);
// no neighbours, it must be a point
unset($points[$re]);
$starter = 1;
scope_it();
} elseif ($got == $re) {
$starter = 1;
examine($results);
// go to check if result is a polygon or a line
} else {
// end of object not reached, carry on sniffing along line till back to start
$starter = $pos + 5;
if ($starter > 8) {
$starter = $starter - 8;
}
//change start point in next search in order to avoid coming back to previous point in the line (unless all the way around
clock($got);
}
}
function get_poss($targ) {
global $poss, $div, $points;
$poss = array();
$sn = explode(":", $targ);
$sn = explode(":", $targ);
$poss[1] = number_format($sn[0] - $div, 2) . ":" . $sn[1];
$poss[2] = number_format($sn[0] - $div, 2) . ":" . number_format($sn[1] + $div, 2);
$poss[3] = $sn[0] . ":" . number_format($sn[1] + $div, 2);
$poss[4] = number_format($sn[0] + $div, 2) . ":" . number_format($sn[1] + $div, 2);
$poss[5] = number_format($sn[0] + $div, 2) . ":" . $sn[1];
$poss[6] = number_format($sn[0] + $div, 2) . ":" . number_format($sn[1] - $div, 2);
$poss[7] = $sn[0] . ":" . number_format($sn[1] - $div, 2);
$poss[8] = number_format($sn[0] - $div, 2) . ":" . number_format($sn[1] - $div, 2);
//compile array of possible neighbours to a point
}
function get_dia($quo) {
global $div;
$poss = array();
$sn = explode(" ", $quo);
$div2 = $div / 2;
$poss[4] = "[" . $sn[0] . ", " . number_format($sn[1] - $div2, 3) . "]";
$poss[1] = "[" . number_format($sn[0] - $div2, 3) . ", " . $sn[1] . "]";
$poss[2] = "[" . $sn[0] . ", " . number_format($sn[1] + $div2, 3) . "]";
$poss[3] = "[" . number_format($sn[0] + $div2, 3) . ", " . $sn[1] . "]";
$poss[0] = "[" . $sn[0] . ", " . number_format($sn[1] - $div2, 3) . "]";
//get cordinate of diamond around points to convert them to small polygon/diamond
return implode(",", $poss);
}
function examine($polygon) {
// run through polygon check
global $points, $lines, $polys, $cnt_p, $n, $s, $w, $e, $in_points, $pts, $geofence;
$poly = 0;
$in_points = array();
$n = -180;
$s = 180;
$w = 90;
$e = -90;
$geofence = new Polygon();
foreach ($polygon as $k => $v) {
$sn = explode(" ", $v);
$geofence->addPoint(new Coordinate($sn[1], $sn[0]));
$key = $sn[0] . ":" . $sn[1];
$in_points[$key] = $v;
unset($points[$key]);
unset($search[$sn[0]][$sn[1]]);
}
$area = $geofence->getArea();
if ($area > 0) {
foreach ($points as $key => $point) {
$pt = explode(" ", $point);
$Point = new Coordinate($pt[1], $pt[0]);
if (($geofence->contains($Point)) == 1) {
$in_points[$key] = $points[$key];
// put points into array to be searched for holes (rings)
unset($points[$key]);
//find area square if new polygon
if ($pt[0] > $n)
$n = $pt[0];
if ($pt[0] < $s)
$s = $pt[0];
if ($pt[1] < $w)
$w = $pt[1];
if ($pt[1] > $e)
$e = $pt[1];
}
}
if (count($in_points) > 9) {
rings($polygon, $in_points, $cnt_p);
}
$polys[$cnt_p] = $polygon;
++$cnt_p;
} else {
$break = array_unique($polygon);
foreach ($break as $k => $v) {
array_push($pts, $v);
}
}
scope_it();
}
function rings($polygon, $in_points, $cnt_p) {
global $n, $s, $w, $e, $div, $ring_s, $holes, $geofence;
// create array same area sqaure as polygon
$ring_s = array();
for ($a = $w; $a <= $e; $a = $a + $div) {
for ($b = $n; $b >= $s; $b = $b - $div) {
$val = number_format($b, 2) . " " . number_format($a, 2);
$key = number_format($b, 2) . ":" . number_format($a, 2);
$ring_s[$key] = $val;
}
}
// remove points in new array outside of polygon
foreach ($ring_s as $key => $point) {
$pt = explode(":", $key);
$Point = new Coordinate($pt[1], $pt[0]);
if (($geofence->contains($Point)) == 0) {
unset($ring_s[$key]);
}
}
foreach ($polygon as $k => $v) {
$sn = explode(" ", $v);
$key = $sn[0] . ":" . $sn[1];
$ring_s[$key] = $v;
}
foreach ($in_points as $k => $v) {
$find = isset($ring_s[$k]);
//deduct from new array ponits found in those to be sorted, leaves any holes in polygon
if ($find !== FALSE) {
unset($ring_s[$k]);
}
}
$cn_r = count($ring_s);
if ($cn_r > 0) {
$holes[$cnt_p] = array();
scope_r($cnt_p);
}
}
function scope_r($cnt_p) {
global $ring_s, $in_points, $poss, $motion, $re, $results, $starter, $previous;
// cycle through remains of the new array W>E and N>S looking for ones that are neigbouring vertices of holes in points to de sorted. When one is found jump onto that vertices and find the other points of internal polygon/edge.
if ($ring_s) {
$key = key($ring_s);
$val = reset($ring_s);
get_poss($key);
$got = NULL;
for ($i = 1; $i <= $motion; ++$i) {
$find = isset($in_points[$poss[$i]]);
if ($find !== FALSE) {
$got = $poss[$i];
$pos = $i;
$i = $motion + 1;
}
}
if ($got !== NULL) {
$results = array();
$re = $got;
array_push($results, $in_points[$re]);
$starter = $pos + 4;
if ($starter > 8) {
$starter = $starter - 8;
}
clock_r($cnt_p, $got);
// jump to search set of points
} else {
unset($ring_s[$key]);
scope_r($cnt_p);
}
}
}
function clock_r($cnt_p, $targ) {
global $poss, $starter, $in_points, $motion, $re, $results, $holes, $ring_s;
get_poss($targ);
//get box of points to search for neighbours
$got = NULL;
for ($i = 0; $i <= $motion - 1; ++$i) {
// search through neighbours for internal hole edges
$pos = $i + $starter;
$find = isset($in_points[$poss[$pos]]);
if ($find !== FALSE) {
$got = $poss[$pos];
array_push($results, $in_points[$got]);
$i = $motion + 1;
} elseif ($pos == $motion) {
$starter = -$i;
}
}
if ($got == $re) {
$starter = 1;
$poly = $results;
array_push($holes[$cnt_p], array_reverse($poly));
$geo2 = new Polygon();
foreach ($poly as $k => $v) {
$sn = explode(" ", $v);
$geo2->addPoint(new Coordinate($sn[1], $sn[0]));
}
foreach ($ring_s as $key => $point) {
$pt = explode(":", $key);
$Point = new Coordinate($pt[1], $pt[0]);
if (($geo2->contains($Point)) == 1) {
unset($ring_s[$key]);
}
}
//when whole of internal polygon found ($re is start/end point), add it to array of holes per main polygon, reversing array order as these should be clockwise and main polygon anti-clockwise. Delete points internal to hole searching array and find any other holes left to record in that array
scope_r($cnt_p);
} else {
$starter = $pos + 5;
if ($starter > 8) {
$starter = $starter - 8;
}
clock_r($cnt_p, $got);
}
}
$starter = 1;
// start position for searching through neighbours
scope_it();
此处显示的数组输出http://wheat-gateway.org.uk/json_map_final.php?ord=4&cns=109&ctrl_r=1//591/923,2//-5/31&ctrl=1,2,这反过来又创建了http://www.wheat-gateway.org.uk/climate_search.php?ord=4&cns=109&ctrl_r=1//591/923,2//-5/31&ctrl=1,2