function close_ways($input_osm_ways, $verbose, $tolerance, $debug)
{
    if ($verbose) {
        error_log("Starting close_ways()");
    }
    $bucket_debug = $debug && false;
    $bucket_grid = new BucketGrid($tolerance * 2);
    $input_ways =& $input_osm_ways->ways;
    $input_nodes =& $input_osm_ways->nodes;
    $way_index = 0;
    foreach ($input_ways as $way_id => &$way) {
        $way_index += 1;
        if ($verbose && $way_index % 100 === 0) {
            error_log("Bucketed {$way_index}/" . count($input_ways));
        }
        if ($way['is_closed'] === true) {
            continue;
        }
        $nds = $way['nds'];
        $nds_count = count($nds);
        if ($nds_count < 2) {
            continue;
        }
        $start_index = 0;
        $end_index = $nds_count - 1;
        $start_nd_ref = $nds[$start_index];
        $end_nd_ref = $nds[$end_index];
        $start_node = $input_nodes[$start_nd_ref];
        $end_node = $input_nodes[$end_nd_ref];
        $start_lat = $start_node['lat'];
        $start_lon = $start_node['lon'];
        $end_lat = $end_node['lat'];
        $end_lon = $end_node['lon'];
        $start_data = array('way_id' => $way_id, 'nd_ref' => $start_nd_ref, 'index' => $start_index, 'lat' => $start_lat, 'lon' => $start_lon);
        $end_data = array('way_id' => $way_id, 'nd_ref' => $end_nd_ref, 'index' => $end_index, 'lat' => $end_lat, 'lon' => $end_lon);
        if ($bucket_debug) {
            error_log("insert_point({$start_lon}, {$start_lat}, " . print_r($start_data, true) . ")");
        }
        $bucket_grid->insert_point($start_lon, $start_lat, $start_data, $bucket_debug);
        if ($bucket_debug) {
            error_log("insert_point({$end_lon}, {$end_lat}, " . print_r($end_data, true) . ")");
        }
        $bucket_grid->insert_point($end_lon, $end_lat, $end_data, $bucket_debug);
    }
    if ($bucket_debug) {
        error_log("bucket_grid: " . print_r($bucket_grid->buckets, true));
    }
    $result = new OSMWays();
    $way_index = 0;
    foreach ($input_ways as $way_id => &$way) {
        $way_index += 1;
        if ($verbose && $way_index % 100 === 0) {
            error_log("Closing {$way_index}/" . count($input_ways));
        }
        if ($way['is_closed'] === true) {
            if ($debug) {
                error_log("Found pre-closed way");
            }
            $result->copy_way($way, $input_osm_ways);
            continue;
        }
        if (!empty($way['is_used'])) {
            if ($debug) {
                error_log("Found already used way");
            }
            continue;
        }
        $input_tags = $way['tags'];
        if (count($way['nds']) < 2) {
            if ($debug) {
                error_log("Too few nodes found for {$way_id}");
            }
            continue;
        }
        $start_index = 0;
        $start_way_id = $way_id;
        $start_nd_ref = $way['nds'][$start_index];
        $start_node = $input_nodes[$start_nd_ref];
        $follow_way_id = $way_id;
        $nds = $way['nds'];
        if ($debug) {
            error_log("Looking at {$follow_way_id}");
        }
        $output_nds = array();
        $loop_count = 0;
        while (true) {
            $input_ways[$follow_way_id]['is_used'] = true;
            $nds_count = count($nds);
            $end_index = $nds_count - 1;
            $output_nds = array_merge($output_nds, $nds);
            $end_nd_ref = $nds[$end_index];
            $end_node = $input_nodes[$end_nd_ref];
            if ($debug) {
                error_log("End node is {$end_nd_ref} at (" . $end_node['lat'] . "," . $end_node['lon'] . ")");
            }
            $distance_to_start = get_node_distance($end_node, $start_node);
            // Have we looped back around to the start?
            if ($distance_to_start < $tolerance) {
                if ($debug) {
                    error_log("Closed way starting with {$start_way_id}, ending with {$follow_way_id}");
                }
                $output_nds[] = $start_nd_ref;
                $result->begin_way();
                foreach ($output_nds as $nd_ref) {
                    $node = $input_nodes[$nd_ref];
                    $result->add_vertex($node['lat'], $node['lon'], $bucket_debug);
                }
                foreach ($input_tags as $key => $value) {
                    $result->add_tag($key, $value);
                }
                $result->end_way();
                break;
            }
            // Figure out which lines we can connect to the end of this one
            $end_lat = $end_node['lat'];
            $end_lon = $end_node['lon'];
            $nearby_points = $bucket_grid->find_points_near($end_lon, $end_lat, $tolerance, $bucket_debug);
            if ($bucket_debug) {
                error_log("find_points_near({$end_lon}, {$end_lat}, {$tolerance}) returned " . print_r($nearby_points, true));
            }
            $closest_distance = null;
            foreach ($nearby_points as $bucket_entry) {
                $point_data = $bucket_entry['data'];
                $point_lat = $point_data['lat'];
                $point_lon = $point_data['lon'];
                $point_way_id = $point_data['way_id'];
                $point_nd_ref = $point_data['nd_ref'];
                if ($point_nd_ref === $end_nd_ref && $point_way_id === $follow_way_id) {
                    if ($bucket_debug) {
                        error_log("{$point_nd_ref} was the same as the start");
                    }
                    continue;
                }
                $distance = get_node_distance($point_data, $end_node);
                if ($bucket_debug) {
                    error_log("{$point_nd_ref} was {$distance} away");
                }
                if ($closest_distance === null || $distance < $closest_distance) {
                    $closest_distance = $distance;
                }
            }
            // Have we reached the end of the line?
            if (!isset($closest_distance)) {
                if ($debug) {
                    error_log("No close points found for {$follow_way_id}");
                }
                break;
            }
            $found_nodes = array();
            foreach ($nearby_points as $bucket_entry) {
                $point_data = $bucket_entry['data'];
                $point_lat = $point_data['lat'];
                $point_lon = $point_data['lon'];
                $point_way_id = $point_data['way_id'];
                $point_nd_ref = $point_data['nd_ref'];
                if ($point_nd_ref === $end_nd_ref && $point_way_id === $follow_way_id) {
                    continue;
                }
                $distance = get_node_distance($point_data, $end_node);
                if ($distance < $closest_distance + EPSILON) {
                    $found_nodes[] = $point_data;
                }
            }
            // Figure out which way edge to follow
            $found_nodes_count = count($found_nodes);
            if ($found_nodes_count === 1) {
                if ($debug) {
                    error_log("A single close point found for {$follow_way_id}");
                }
                $follow_node_index = 0;
            } else {
                if ($debug) {
                    error_log("Too many close points found for {$follow_way_id}");
                }
                break;
            }
            $follow_node = $found_nodes[$follow_node_index];
            $follow_way_id = $follow_node['way_id'];
            $follow_way = $input_ways[$follow_way_id];
            $do_reverse = $follow_node['index'] > 0;
            $nds = $follow_way['nds'];
            if ($do_reverse) {
                $nds = array_reverse($nds);
            }
            $loop_count += 1;
            if ($loop_count > 1000) {
                die("Looped too many times\n");
            }
        }
    }
    if ($verbose) {
        error_log("Finished close_ways()");
    }
    return $result;
}
function extract_ways_matching_keys(&$input_osm_ways, $match_expression, $verbose)
{
    if ($verbose) {
        error_log("Starting way filtering");
    }
    $input_nodes = $input_osm_ways->nodes;
    $input_ways = $input_osm_ways->ways;
    $result = new OSMWays();
    $count = 0;
    foreach ($input_ways as $input_way) {
        $tags = $input_way['tags'];
        if (evaluate_match_expression($tags, $match_expression)) {
            $result->copy_way($input_way, $input_osm_ways);
        }
        $count += 1;
        if ($verbose && $count % 1000 === 0) {
            error_log("Processed {$count}/" . count($input_ways));
        }
    }
    if ($verbose) {
        error_log("Finished way filtering");
    }
    return $result;
}
function merge_nodes_on_edges($input_osm_ways, $verbose, $tolerance, $debug)
{
    if ($verbose) {
        error_log("Starting merge_nodes_on_edges()");
    }
    $bucket_debug = $debug;
    $input_ways =& $input_osm_ways->ways;
    $input_nodes =& $input_osm_ways->nodes;
    $result = new OSMWays();
    $way_index = 0;
    foreach ($input_ways as $way_id => $way) {
        $way_index += 1;
        //        if ($verbose&&(($way_index%100)===0))
        error_log("Merged {$way_index}/" . count($input_ways));
        $nds = $way['nds'];
        $nds_count = count($nds);
        if ($nds_count < 2) {
            $result->copy_way($way, $input_osm_ways);
            if ($debug) {
                error_log("Skipping {$way_id}");
            }
            continue;
        }
        $result->begin_way($way_id);
        foreach ($way['tags'] as $key => $value) {
            $result->add_tag($key, $value);
        }
        $nds_map = array_count_values($nds);
        $node_index = 0;
        foreach ($nds as $nd_ref) {
            $is_last = $node_index == $nds_count - 1;
            $node_index += 1;
            if (!isset($input_nodes[$nd_ref])) {
                if ($debug) {
                    error_log("Missing node {$nd_ref} in {$way_id}");
                }
                continue;
            }
            $node = $input_nodes[$nd_ref];
            $start_x = $node['lat'];
            $start_y = $node['lon'];
            if ($debug) {
                error_log("Adding original {$nd_ref} ({$start_x}, {$start_y})");
            }
            $result->add_vertex($start_x, $start_y);
            if ($is_last) {
                continue;
            }
            $end_nd_ref = $nds[$node_index];
            $end_node = $input_nodes[$end_nd_ref];
            $end_x = $end_node['lat'];
            $end_y = $end_node['lon'];
            $coincident_points = $input_osm_ways->bucket_grid->find_points_near_line($start_x, $start_y, $end_x, $end_y, $tolerance, $bucket_debug);
            $sortfunction = create_function('$a, $b', 'if ($a["output_s"]>$b["output_s"]) return 1; else return -1;');
            usort($coincident_points, $sortfunction);
            foreach ($coincident_points as $point) {
                $s = $point['output_s'];
                if ($s < 0.0 || $s > 1.0) {
                    continue;
                }
                $point_nd_ref = $point['data']['id'];
                if (isset($nds_map[$point_nd_ref])) {
                    continue;
                }
                if ($debug) {
                    error_log("Adding {$point_nd_ref}");
                }
                $point_x = $point['x'];
                $point_y = $point['y'];
                $result->add_vertex($point_x, $point_y);
            }
        }
        $result->end_way();
    }
    if ($verbose) {
        error_log("Finished close_ways()");
    }
    return $result;
}