diff --git a/obs/bin/obs_face.py b/obs/bin/obs_face.py index dbee450..9616775 100644 --- a/obs/bin/obs_face.py +++ b/obs/bin/obs_face.py @@ -356,6 +356,9 @@ def main(): parser.add_argument('-v', '--verbose', action='store_true', help='be verbose') + parser.add_argument('-c', '--chunk', required=False, action='store', default=100, type=float, + help='Chop segments on road page to max length in meter.') + args = parser.parse_args() coloredlogs.install(level=logging.DEBUG if args.verbose else logging.INFO, @@ -393,6 +396,8 @@ def main(): if args.annotate or args.collect or args.visualization: logging.info('Loading OpenStreetMap data') map_source = OSMDataSource(cache_dir=args.path_cache) + if args.chunk is not None: + map_source.chunk_size = args.chunk if args.annotate or args.collect: if not args.input: diff --git a/obs/face/geojson/ExportRoadAnnotations.py b/obs/face/geojson/ExportRoadAnnotations.py index e399fd8..f216bd3 100644 --- a/obs/face/geojson/ExportRoadAnnotations.py +++ b/obs/face/geojson/ExportRoadAnnotations.py @@ -43,16 +43,26 @@ def add_measurements(self, measurements): for sample in measurements: self.n_samples += 1 # filter measurements + if not ("OSM_way_id" in sample): + continue; + way_id = sample["OSM_way_id"] + way_orientation = 1 if sample["OSM_way_orientation"] == -1 else 0 + if sample["latitude"] is None or sample["longitude"] is None or sample["distance_overtaker"] is None \ or self.only_confirmed_measurements and (sample["confirmed"] is not True) \ or not sample["has_OSM_annotations"]: + if not (way_id in self.way_statistics): + way = self.map_source.get_way_by_id(way_id) + if way: + self.way_statistics[way_id] = WayStatistics(way_id, way) + if way_id in self.way_statistics and sample["speed"] != 0: + self.way_statistics[way_id].n_ticks[way_orientation] += 1 + continue self.n_valid += 1 - way_id = sample["OSM_way_id"] value = sample["distance_overtaker"] - way_orientation = sample["OSM_way_orientation"] self.map_source.ensure_coverage([sample["latitude"]], [sample["longitude"]]) @@ -68,13 +78,15 @@ def add_measurements(self, measurements): self.n_grouped += 1 else: logging.warning("way not found in map") + self.way_statistics[way_id].n_ticks[way_orientation] += 1 def finalize(self): log.info("%s samples, %s valid", self.n_samples, self.n_valid) features = [] for way_stats in self.way_statistics.values(): way_stats.finalize() - if not any(way_stats.valid): +# if not any(way_stats.valid): + if not any(way_stats.n_ticks): continue for i in range(1 if way_stats.oneway else 2): @@ -91,19 +103,14 @@ def finalize(self): coordinates = [] feature = {"type": "Feature", - "properties": {"distance_overtaker_mean": way_stats.d_mean[i], - "distance_overtaker_median": way_stats.d_median[i], - "distance_overtaker_minimum": way_stats.d_minimum[i], - "distance_overtaker_n": way_stats.n[i], - "distance_overtaker_n_below_limit": way_stats.n_lt_limit[i], - "distance_overtaker_n_above_limit": way_stats.n_geq_limit[i], - "distance_overtaker_limit": way_stats.d_limit, - "distance_overtaker_measurements": way_stats.samples[i], + "properties": {"distance_overtaker_limit": way_stats.d_limit, + "distance_overtaker_measurements": sorted(way_stats.samples[i], key = float), "zone": way_stats.zone, "direction": direction, "name": way_stats.name, "way_id": way_stats.way_id, "valid": way_stats.valid[i], + "ticks": way_stats.n_ticks[i], }, "geometry": {"type": "LineString", "coordinates": coordinates}} @@ -124,12 +131,10 @@ def __init__(self, way_id, way): self.n = [0, 0] self.n_lt_limit = [0, 0] self.n_geq_limit = [0, 0] + self.n_ticks = [0, 0] self.way_id = way_id self.valid = [False, False] - self.d_mean = [0, 0] - self.d_median = [0, 0] - self.d_minimum = [0, 0] self.zone = "unknown" self.oneway = False @@ -156,19 +161,11 @@ def __init__(self, way_id, way): def add_sample(self, sample, orientation): if np.isfinite(sample): - i = 1 if orientation == -1 else 0 - self.samples[i].append(sample) + self.samples[orientation].append(sample) return self def finalize(self): for i in range(2): samples = np.array(self.samples[i]) if len(samples) > 0: - self.n[i] = len(samples) - self.d_mean[i] = np.mean(samples) - self.d_median[i] = np.median(samples) - self.d_minimum[i] = np.min(samples) - if self.d_limit is not None: - self.n_lt_limit[i] = int((samples < self.d_limit).sum()) - self.n_geq_limit[i] = int((samples >= self.d_limit).sum()) self.valid[i] = True diff --git a/obs/face/osm/DataSource.py b/obs/face/osm/DataSource.py index fb536d8..66da862 100644 --- a/obs/face/osm/DataSource.py +++ b/obs/face/osm/DataSource.py @@ -36,6 +36,7 @@ def __init__(self, cache_dir="cache", tile_zoom=14): self.loaded_tiles = [] self.tile_source = TileSource() self.tile_zoom = tile_zoom + self.chunk_size = 100 def ensure_coverage(self, lat, lon, extend=0.0): tiles = self.tile_source.get_required_tiles(lat, lon, self.tile_zoom, extend=extend) @@ -65,9 +66,10 @@ def add_tile(self, tile): # add way objects, and store for way_id, way in ways.items(): if way_id not in self.ways: - w = Way(way_id, way, nodes) - self.ways[way_id] = w - self.way_container.insert(w) + w = Way.create(way_id, way, nodes, self.chunk_size) + self.ways.update(w) + for id in w: + self.way_container.insert(w[id]) # update tile list self.loaded_tiles.append(tile) diff --git a/obs/face/osm/Way.py b/obs/face/osm/Way.py index 689c742..6075051 100644 --- a/obs/face/osm/Way.py +++ b/obs/face/osm/Way.py @@ -4,7 +4,7 @@ class Way: - def __init__(self, way_id, way, all_nodes): + def __init__(self, way_id, way, nodes_way): self.way_id = way_id if "tags" in way: @@ -13,8 +13,6 @@ def __init__(self, way_id, way, all_nodes): self.tags = {} # determine points - nodes_way = [all_nodes[i] for i in way["nodes"]] - lat = np.array([n["lat"] for n in nodes_way]) lon = np.array([n["lon"] for n in nodes_way]) self.points_lat_lon = np.stack((lat, lon), axis=1) @@ -35,10 +33,78 @@ def __init__(self, way_id, way, all_nodes): # direction dx = np.diff(x) dy = np.diff(y) + self.seg_length = np.hypot(dx, dy) self.direction = np.arctan2(dy, dx) self.directionality_bicycle, self.directionality_motorized = self.get_way_directionality(way) + @staticmethod + def subid(way_id, nodes): + if len(nodes) == 0: + return way_id + return str(way_id)+'.'+str(len(nodes)) + + @staticmethod + def create(way_id, way, all_nodes, max_len): + ways = {} + # determine points + nodes = [all_nodes[i] for i in way["nodes"]] + lat = np.array([n["lat"] for n in nodes]) + lon = np.array([n["lon"] for n in nodes]) + + # bounding box + a = (min(lat), min(lon)) + b = (max(lat), max(lon)) + + # define the local map around the center of the bounding box + lat_0 = (a[0] + b[0]) * 0.5 + lon_0 = (a[1] + b[1]) * 0.5 + local_map = LocalMap(lat_0, lon_0) + x, y = local_map.transfer_to(lat, lon) + dx = np.diff(x) + dy = np.diff(y) + seg_length = np.hypot(dx, dy) + slen = 0 + newnodes = [nodes[0]] + first = 0 + # split long segments with intermediate nodes + for i in range(len(seg_length)): + n = math.floor((seg_length[i] - max_len / 2) / max_len) + if n > 0: + for s in range(n): + f1 = (n - s) / (n + 1) + f2 = (s + 1) / (n + 1) + latx = lat[i] * f1 + lat[i+1] * f2 + lonx = lon[i] * f1 + lon[i+1] * f2 + if math.isnan(latx) or math.isnan(lonx): + f1 = f2 + node_id = nodes[i]['id']+ (s+1) * 0x1000000 + node = {'type':'node', 'id':node_id, 'lat':latx, 'lon':lonx} + all_nodes[node_id] = node + newnodes.append(node) + w_id = Way.subid(way_id, ways) + ways[w_id] = Way(w_id, way, newnodes[first:]) + first = len(newnodes) - 1 + slen = 0 + # add last segment + newnodes.append(nodes[i+1]) + w_id = Way.subid(way_id, ways) + ways[w_id] = Way(w_id, way, newnodes[first:]) + first = len(newnodes) - 1 + slen = 0 + else: + newnodes.append(nodes[i+1]) + slen += seg_length[i] + if (slen > max_len and i != first): + w_id = Way.subid(way_id, ways) + ways[w_id] = Way(w_id, way, newnodes[first:]) + first = len(newnodes) - 1 + slen = 0 + if slen > 0: + w_id = Way.subid(way_id, ways) + ways[w_id] = Way(w_id, way, newnodes[first:]) + return ways + def get_axis_aligned_bounding_box(self): return self.a, self.b @@ -118,6 +184,8 @@ def get_way_coordinates(self, reverse=False, lateral_offset=0): # then move the point c_i = c[i] + n_i * lateral_offset c_i = self.local_map.transfer_from(c_i[0], c_i[1]) + if math.isnan(c_i[0]) or math.isnan(c_i[1]): + n_next = 0 coordinates.append([c_i[0], c_i[1]]) return coordinates diff --git a/visualization/OBS.js b/visualization/OBS.js index 7a66aac..b3378bf 100644 --- a/visualization/OBS.js +++ b/visualization/OBS.js @@ -172,11 +172,12 @@ class Palette { paletteUrban = new Palette( { - 0.0: [64, 0, 0, 255], - 1.4999: [196, 0, 0, 255], - 1.5: [196, 196, 0, 255], - 2.0: [0, 196, 0, 255], - 2.55: [0, 255, 0, 255], + 0.0: [196, 0, 0, 255], + 0.8: [196, 0, 0, 255], + 1.3: [245, 141, 0, 255], + 1.5: [94, 188, 7, 255], + 2.0: [94, 188, 7, 255], + 2.55: [0, 196, 0, 255], }, [0, 0, 196, 255] ) diff --git a/visualization/measurements.html b/visualization/measurements.html index f1194c5..bd68bcd 100644 --- a/visualization/measurements.html +++ b/visualization/measurements.html @@ -104,43 +104,39 @@