function save_line_query($points, $spec) { $query = ''; //identify line and points fields based on the table spec list($line_fld, $points_fld) = explode('/', clb_val(FALSE, $spec, 'polyline') . '/'); if ($points && ($mid = pline_midpoint($points))) { list($_REQUEST['lat'], $_REQUEST['lng']) = $mid; } $pt = reset($points); $_REQUEST['end1'] = clb_b64e($pt[0], 10000.0) . clb_b64e($pt[1], 10000.0); $pt = end($points); $_REQUEST['end2'] = clb_b64e($pt[0], 10000.0) . clb_b64e($pt[1], 10000.0); if ($points_fld) { $val = ''; foreach ($points as $pt) { $val .= join(',', $pt) . "\n"; } $query .= '`' . $points_fld . '`=' . clb_escape($val) . ','; } if ($line_fld) { $line = $points ? pline_make($points, array('color' => '#0000FF')) : ''; if ($line) { $line = clb_join($line, '', '&', '='); } $query .= '`' . $line_fld . '`=' . clb_escape($line) . ','; } return $query; }
function pbuild_daisychain($rnums, $primitives, $stopsegs) { global $wpdb; if (!is_array($rnums)) { $rnums = array($rnums); } $now = time(); $processed = clb_now_utc($now); $links_count = 0; //platforms mapped to stations with converging lines $plats = pbuild_platforms(); $stat2plat = $plats['stat2plat']; $plat2stat = $plats['plat2stat']; //select route records that list stops rather than segments $query = 'SELECT ' . RF_ROUTES_SELECT . ',' . RF_ROUTES_STOPS . ',' . RF_ROUTES_SBACK . ' FROM ' . RF_ROUTES_FROM . ' WHERE ' . RF_ROUTES_KEY . ' IN ' . clb_join($rnums, TRUE); $routes = $wpdb->get_results($query, ARRAY_A); if (is_array($routes)) { $max_routes = count($routes); foreach ($routes as $rt_no => $route) { if ($rt_no % round($max_routes / 100) == 0) { echo round(100 * ($rt_no / $max_routes)) . '% complete' . "\n"; } $rnum = clb_val(FALSE, $route, RF_ROUTES_KEY); $ptype = clb_val(FALSE, $route, RF_ROUTES_TYPE); if (!$ptype) { qlog(__FUNCTION__, __LINE__, 'route did not have ptype which is necessary to give the result links types', $route); continue; } //assume a route is reversable unless it is blocked (by '*' in the field making it not //empty but not listing anythign) or explicitly lists the reverse version if (empty($route[RF_ROUTES_SBACK])) { $lines = preg_split('/[\\r\\n]+/', $route[RF_ROUTES_STOPS]); $route[RF_ROUTES_SBACK] = join("\n", array_reverse($lines)) . "\n"; } //scanning directions separately foreach (array(RF_ROUTES_STOPS => '', RF_ROUTES_SBACK => '.') as $dir => $dot) { $last_comment = ''; //split out stop pnums from the segs list on the route record if (preg_match_all('/^([\\w<>]+)(\\S*\\s(.*))?$/m', $route[$dir], $lines, PREG_SET_ORDER)) { $nodes1 = FALSE; $max_segs = count($lines); qlog(__FUNCTION__, __LINE__, $max_routes, $rt_no, $max_segs, $rnum); //loop thorugh each stop/station pnum just extracted from stops_list / stops_back foreach ($lines as $ln_no => $stop) { $nodes2 = clb_val(FALSE, $stop, 1); //get stop pnum from the regular expression above if (preg_match('/<\\w+>/', $nodes2)) { //this is a section breaker, this stops the previous and next stops from getting a connecting link //at 09/09/09 there were no routes using this feature. $nodes1 = $nodes2 = FALSE; continue; } /* node2 is the stop/station pnum just extracted from the list above if the station has plaforms we will make nodes2 an array of plafroms but if not we just wrap the station pnum in an array */ $nodes2 = isset($stat2plat[$nodes2]) ? $stat2plat[$nodes2] : array($nodes2); //get the comments after the pnum and look for a time in minutes $comment = clb_val(FALSE, $stop, 3); $mins = FALSE; //get time to travel path as minutes from the end of the comment which will end "*22" if (preg_match('/\\*(\\d+)$/', $comment, $parts)) { $mins = $parts[1]; } $comment = preg_replace('/\\s+\\*(\\d+)$/', '', $comment); /* $segs1/2 will hold an array of segment pnums that are near to the station or platforms in $node1/2 the key of each $segs2 element is also the key to the data for that segment/node intersection in $stopsegs ie $segs1[stop_pnum][$k] = seg_pnum, $stopsegs['data'][$k] == segment data */ $segs2 = array(); foreach ($nodes2 as $k => $pnum2) { $keys = array_keys($stopsegs['stoppnums'], $pnum2); if (!count($keys)) { qlog(__FUNCTION__, __LINE__, 'stop not found in segmap: ', $pnum2); //assume this is a garbage line and remove from array as it will cause more errors later if kept unset($nodes2[$k]); continue; } else { foreach ($keys as $key) { $segs2[$pnum2][$key] = $stopsegs['segpnums'][$key]; } } } //when we have two stops (ie after first loop) we need to find/make a link $links_made = 0; //since matching to any given platform may legitmately fail, count total links within loop and give err after if still 0 $dist = 0; $linkrec = FALSE; if ($nodes1 != FALSE) { $set_set = array(); //in case we get more than one path because of multiple platforms accumulate them in an array first $best_dist = FALSE; //will track the best distance on a set //producting platforms but this will usually be only one node in each array foreach ($nodes1 as $pnum1) { foreach ($nodes2 as $pnum2) { //see if we already have such a link and update it if necessary $linkpnum = FALSE; $process = TRUE; $use_angles = TRUE; $forward = TRUE; $set = array(); $query = 'SELECT ' . RF_LINKS_SELECT . ',' . RF_LINKS_POINTS . ' FROM ' . RF_LINKS_FROM . ' WHERE ' . RF_LINKS_TYPE . '= ' . clb_escape($ptype); $query .= ' AND ((' . RF_LINKS_END1 . '=' . clb_escape($pnum2) . ' AND ' . RF_LINKS_END2 . '=' . clb_escape($pnum1) . ') '; $query .= 'OR (' . RF_LINKS_END1 . '=' . clb_escape($pnum1) . ' AND ' . RF_LINKS_END2 . '=' . clb_escape($pnum2) . '))'; $linkrec = $wpdb->get_results($query, ARRAY_A); //should only be one if (clb_count($linkrec) > 1) { //bad, dont want multiple links between same nodes qlog(__FUNCTION__, __LINE__, 'multiple links for same nodes and mode', $pnum1, $pnum2, array_keys($linkrec)); do_query('DELETE FROM ' . RF_LINKS_FROM . ' WHERE pnum IN ' . clb_join(array_keys($linkrec), TRUE), __FILE__, __LINE__); //delete them all and start again $wpdb->query($query); $linkrec = FALSE; } if (is_array($linkrec)) { //links requiring reversing need to have "<>" added to the title field of the generated link // <>Newark Castle - Newark North Gate - this is across a junction not from a terminus // <>Liskeard- St Keyne - backs out of a terminus before splitting off at next junction $linkrec = reset($linkrec); $set[RF_LINKS_KEY] = $linkrec[RF_LINKS_KEY]; $set[RF_LINKS_DIST] = $linkrec[RF_LINKS_DIST]; $use_angles = !preg_match('/^<>/', $linkrec[RF_LINKS_NAME]); //hand altered links that have <> at the beginning of the name do not require angles to match as trains must reverse $process = clb_get_stamp(clb_val(FALSE, $linkrec, RF_LINKS_MODIFIED)) < $now; //0 if fails /* reverse value of 1 means not in reverse direction, 2 means not in forward direction If the record has not been processed in this session, we start by blocking the other direction but if we end up processing it in the other direction too, we will unblock it. */ $end1 = clb_val(FALSE, $linkrec, RF_LINKS_END1); $forward = $end1 == $pnum1; //direction we are going this time $reverse = clb_val(FALSE, $linkrec, RF_LINKS_REVERSE); //the existing reverse value on the link if ($process) { $set[RF_LINKS_REVERSE] = $forward ? 1 : 2; //first run, block the other direction } else { if ($reverse == 1 && !$forward) { $set[RF_LINKS_REVERSE] = 0; //seen both directions, unblock } else { if ($reverse == 2 && $forward) { $set[RF_LINKS_REVERSE] = 0; //seen both directions, unblock } } } } else { $linkrec = FALSE; $set[RF_LINKS_REVERSE] = 1; //new records are forwards not reverse by definition } /* the only time we dont process is if this has been processed in this run, such as the reverse direction of same route, or two routes with a common path section both stations can be on multiple segs (when near junctions or cross roads) so product combinations and accumulate resulting paths but usually only going to get connections on one of the producted list of segments, so if point A on segs 1 & 2 and point B on segs 3 & 4 may only get connection on 1 with 3 */ if ($process) { $thru_paths = array(); /* we will look at the closeness of stops to different segments and try the one that the stops are closest to first. this not only saves time as we dont try to connect unconnected segs but prevents stops near junctions getting attached to the one they are not closest too $k1 & $k2 are the numerical key values that link the separate parts of $stopsegs $n1 & $n2 are the segment pnums on which the $pnum1 and $pnum2 nodes are near. */ if (count($segs1[$pnum1]) > 1 || count($segs2[$pnum2]) > 1) { $c1 = $c2 = FALSE; foreach ($segs1[$pnum1] as $k1 => $n1) { if (isset($stopsegs['data'][$k1]) && (is_bool($c1) || $stopsegs['data'][$k1]['near'] < $stopsegs['data'][$c1]['near'])) { $c1 = $k1; } } foreach ($segs2[$pnum2] as $k2 => $n2) { if (isset($stopsegs['data'][$k2]) && (is_bool($c2) || $stopsegs['data'][$k2]['near'] < $stopsegs['data'][$c2]['near'])) { $c2 = $k2; } } // ACTUAL CALL TO SHORTEST PATH: pbuild_shortpath(seg_pnum1, seg_pnum2, seg1_data, seg2_data) $thru_paths = pbuild_shortpath($segs1[$pnum1][$c1], $segs2[$pnum2][$c2], $stopsegs['data'][$c1], $stopsegs['data'][$c2], $primitives, $use_angles); //ACTUAL CALL TO SHORTEST PATH } /* if the above managed to find a path from the segments closest to each station then we are done, but if not we now need to loop through the possibilities */ if (count($thru_paths) == 0) { foreach ($segs1[$pnum1] as $k1 => $n1) { foreach ($segs2[$pnum2] as $k2 => $n2) { /* because we are going to do shortest path on segments we need to pass in the actual stop end points because they could be on very long segments ACTUAL CALL TO SHORTEST PATH: pbuild_shortpath(seg_pnum1, seg_pnum2, seg1_data, seg2_data) */ $temp = pbuild_shortpath($n1, $n2, $stopsegs['data'][$k1], $stopsegs['data'][$k2], $primitives, $use_angles); if (clb_count($temp)) { $thru_paths = array_merge($thru_paths, $temp); } //add results after each loop } } } if (count($thru_paths) <= 0) { //if one or both end points was a platform, dont give an error as we expect some platforms to fail if (!array_key_exists($pnum1, $plat2stat) && !array_key_exists($pnum2, $plat2stat)) { qlog(__FUNCTION__, __LINE__, 'no link path found for', $dir, $pnum1, $pnum2, $use_angles ? 'angles' : 'flex', array_key_exists($pnum1, $plat2stat), array_key_exists($pnum2, $plat2stat)); } $set = array(); //clear this to prevent record being saved, because the reverse will have been set } else { /* although each call to pbuild_shortpath can at most return one path, multiple lines or platforms means there are several calls and can result in more than one path. Need to scan results to find shortest. */ $best = FALSE; $min = clb_val(FALSE, $thru_paths, 0, 'dist'); foreach ($thru_paths as $rec) { if (clb_val(FALSE, $rec, 'dist') <= $min) { $best = clb_val(FALSE, $rec, 'path'); } } $links_count++; //qlog(__FUNCTION__, __LINE__, count($thru_paths), 'path found: ', $pnum1, $pnum2, $links_count, $ln_no, $best); /* if we are processing the path, it is the first pass which is usually the forward pass but if one route considers this backwards and another one considers it forwards and the backwards one processes first, then need to reverse this result rather than swapping the end points on the record */ $points = pline_splice($wpdb, $best, $pnum1, $pnum2, $stopsegs); if (!$forward) { $points = array_reverse($points); } if (clb_count($points)) { if (is_numeric($mins)) { $set[RF_LINKS_TIME] = $mins; } if (!$linkrec) { $set[RF_LINKS_END1] = $pnum1; //may already be set $set[RF_LINKS_END2] = $pnum2; } //the polyline is only made for debugging and inspection not for final display $polyline = pline_make($points, array('color' => '#0000FF')); $set[RF_LINKS_LINE] = clb_join($polyline, '', '&', '='); $dist = clb_val(0, $polyline, 'meters'); $set[RF_LINKS_DIST] = $dist; //remove any unused points from the points list and save as a structure $temp = $points; $points = array(); foreach ($temp as $pt) { if (isset($pt['gran'])) { $points[] = $pt; } } $set[RF_LINKS_POINTS] = clb_blob_enc($points); //set the "position" for the segment marker as the mid point if ($midpoint = pline_midpoint($points)) { $set['lat'] = $midpoint[0]; $set['lng'] = $midpoint[1]; $set['elev'] = $midpoint[2]; } //last comment is the previous station/stop name so that this link gets the "A - B" name $name = $last_comment . ' - ' . $comment; if (!$use_angles) { $name = '<>' . $name; } $set[RF_LINKS_NAME] = substr($name, 0, 70); } } } /* we may have more than one set of results if there were multiple routes save each set and remember best distance so we can find the best one below */ if (clb_count($set)) { $set_set[] = $set; if (is_bool($best_dist) || $set[RF_LINKS_DIST] < $best_dist) { $best_dist = $set[RF_LINKS_DIST]; } //will track the best distance on a set } } } //loop producting platforms, only 1 loop if both stations //now only create best distance link $links_made = 0; foreach ($set_set as $set) { /* may need to allow more than one connection eg waterloo to clapham junction, some trails take north platform some south *** the above is true and important, lines may come in as one but then diverge at a station. there needs to be a line in to each of the platforms so that journeys can continue out from both platforms. limit to within 200m of best distance which should include all platforms but wont allow for long alternative routes. */ if ($set[RF_LINKS_DIST] <= $best_dist + 200) { $links_made++; //if the direction is not being set then this signals that nothing else needs saving either if (isset($set[RF_LINKS_REVERSE])) { $set[RF_LINKS_MODIFIED] = $processed; $set[RF_LINKS_TYPE] = $ptype; if (isset($set[RF_LINKS_KEY])) { $query = 'UPDATE ' . RF_LINKS_FROM . ' SET ' . clb_join($set, '"', ',', '=') . ' WHERE ' . RF_LINKS_KEY . '=' . clb_escape($set[RF_LINKS_KEY]); } else { $set[RF_LINKS_KEY] = pbuild_new_pnum(RF_LINKS_FROM, RF_LINKS_KEY); $set[RF_LINKS_CREATED] = $processed; $query = 'INSERT INTO ' . RF_LINKS_FROM . ' SET ' . clb_join($set, '"', ',', '='); qlog(__LINE__, 'inserting', count($set_set), $nodes1, $nodes2, $set[RF_LINKS_DIST], $set[RF_LINKS_END1], $set[RF_LINKS_END2], $set[RF_LINKS_KEY]); } $wpdb->query($query); } } else { if (isset($set[RF_LINKS_KEY])) { //this is a saved record that has been bettered so must be removed qlog(__FUNCTION__, __LINE__, 'deleting bettered link to a platform', $set[RF_LINKS_KEY], $set[RF_LINKS_DIST], $best_dist); pbuild_del_rec(RF_LINKS_FROM, RF_LINKS_KEY . '=' . clb_escape($set[RF_LINKS_KEY])); } } } //if we found an existing link then $linkrec is not false and if links_made >0 then we made one if ($links_made <= 0) { qpre(__FUNCTION__, __LINE__, '***** no paths found between nodes: ', $rnum, join(', ', $nodes1), join(', ', $nodes2), 'p1 on segs: ' . join(', ', $segs1[$pnum1]), 'p2 on segs: ' . join(', ', $segs2[$pnum2]), $comment); } } //have two nodes to link //carry values to the next loop $segs1 = $segs2; $nodes1 = $nodes2; $last_comment = $comment; //carry comment (ie station name) over so next link can get name "this - next" } } } } } }