def intersections_byways(b1, b2): intersections = list() points1 = gml.flat_to_xys(b1.geometry_wkt) points2 = gml.flat_to_xys(b2.geometry_wkt) for i in range(1,len(points1)): for j in range(1, len(points2)): intersection = intersection_segments((points1[i-1], points1[i]), (points2[j-1], points2[j])) if (intersection is not None): intersections.append(intersection) return intersections
def fit_route_step(self, step, at_start): # Get the node ID and node x,y from the byway for this route step. That # is, instead of using the geocoded xy, use Cyclopath's byway's xy. # FIXME: If you request a route from an address in the middle of the # street, doesn't think move the point to the nearest # intersection?? (TEST: 5038 dupont ave s) # FIXED?: Was: geom = gml.flat_to_xys(step.geometry[2:]) geom = gml.flat_to_xys(step.geometry) if at_start: if step.forward: self.node_id = step.beg_node_id self.x = geom[0][0] self.y = geom[0][1] else: self.node_id = step.fin_node_id self.x = geom[-1][0] self.y = geom[-1][1] else: if step.forward: self.node_id = step.fin_node_id self.x = geom[-1][0] self.y = geom[-1][1] else: self.node_id = step.beg_node_id self.x = geom[0][0] self.y = geom[0][1]
def fix_onepoint(block): points = gml.flat_to_xys(block.geometry_wkt) if len(points) == 3: # remove extra point, we don't need it anymore del points[1] block.geometry_wkt = ( 'LINESTRING(%s)' % (gml.wkt_coords_format(points),))
def fit_route_step(self, step, at_start): # Get the node ID and node x,y from the byway for this route step. That # is, instead of using the geocoded xy, use Cyclopath's byway's xy. # FIXME: If you request a route from an address in the middle of the # street, doesn't think move the point to the nearest # intersection?? (TEST: 5038 dupont ave s) # FIXED?: Was: geom = gml.flat_to_xys(step.geometry[2:]) geom = gml.flat_to_xys(step.geometry) if at_start: if step.forward: self.node_id = step.beg_node_id self.x = geom[0][0] self.y = geom[0][1] else: self.node_id = step.fin_node_id self.x = geom[-1][0] self.y = geom[-1][1] else: if step.forward: self.node_id = step.fin_node_id self.x = geom[-1][0] self.y = geom[-1][1] else: self.node_id = step.beg_node_id self.x = geom[0][0] self.y = geom[0][1]
def save_core(self, qb): item_user_watching.One.save_core(self, qb) # A branch's stack ID always the same as its branch ID (self-referential) g.assurt( self.stack_id == self.branch_id) # see save_core_get_branch_id # NOTE: We only honor create-branch and delete from local requests. From # flashclient, we just support renaming. g.assurt(((self.version > 1) and not self.deleted) or qb.request_is_local) # NOTE: We've already verified the parent ID is valid. If the item is # fresh, we've checked that the user has permissions to make a copy of # the parent. If not, we've checked the user can edit the branch. if self.fresh: self.parent_id = qb.branch_hier[0][0] else: g.assurt((not self.parent_id) or (self.parent_id == qb.branch_hier[1][0])) # The user is allowed to set self.last_merge_rid only once: when the # branch is created. Once the branch exists, the user can only change # last_merge_rid by performing an update. # 2013.04.04: And remember to use rid_new and not rid_max. if self.last_merge_rid is None: self.last_merge_rid = qb.item_mgr.rid_new else: # NO: Allow update: g.assurt(self.fresh and (self.version == 1)) if ((self.last_merge_rid < 0) or (self.last_merge_rid > qb.item_mgr.rid_new)): raise GWIS_Error('last_merge_rid out-of-bounds: %d' % (self.last_merge_rid, )) # We fetch coverage_area as SVG (which is what all the append_* fcns. # expect), but PostGIS has no SVG import function (weird, [lb] thinks). # 2014.04.27: Rather than just repeat what's in the database for the # prevision branch version (and maybe losing precision since # we fetched coverage_area using conf.db_fetch_precision), # we can recalculate it: see: commit.py calls # byway.Many.branch_coverage_area_update. restore_carea = self.coverage_area if self.coverage_area.startswith('M '): # There's no geometry.svg_polygon_to_xy like there's a svg_line_to_xy, # so we use the geometry-type agnostic gml.flat_to_xys rather than # just filling in the blanks in geometry.py and writing such a fcn. # And this is a little ugly: we have to close the polygon ring, and # we have to make it a multipolygon for our geometry translation. # (Instead of this dance, we could, e.g., # ST_AsEWKT(br.coverage_area) AS coverage_area_wkt.) # Note that we fetched coverage_area using conf.db_fetch_precision, # so we've lost precision, so the caller should make sure to call # byway.Many.branch_coverage_area_update if they care (commit does). coverage_pgon = gml.flat_to_xys(self.coverage_area) coverage_pgon.append(coverage_pgon[0]) self.coverage_area = geometry.xy_to_ewkt_polygon( [ coverage_pgon, ], precision=conf.postgis_precision) self.save_insert(qb, One.item_type_table, One.psql_defns) # Restore the geometry to SVG (even though in practive save_core # is just used by commit, so the coverage_area is no longer needed, # and is about to be recomputed). self.coverage_area = restore_carea
def dist_ob_block(self, ob, b): geom = gml.flat_to_xys(b.geometry_wkt) result = dist_ob_line_segment(ob, geom[0], geom[1]) for n in range(1, len(geom)-1): new_dist = dist_ob_line_segment(ob, geom[n], geom[n+1]) if new_dist < result: result = new_dist return result, dist(ob, geom[0]), dist(ob, geom[-1])
def save_core(self, qb): item_user_watching.One.save_core(self, qb) # A branch's stack ID always the same as its branch ID (self-referential) g.assurt(self.stack_id == self.branch_id) # see save_core_get_branch_id # NOTE: We only honor create-branch and delete from local requests. From # flashclient, we just support renaming. g.assurt(((self.version > 1) and not self.deleted) or qb.request_is_local) # NOTE: We've already verified the parent ID is valid. If the item is # fresh, we've checked that the user has permissions to make a copy of # the parent. If not, we've checked the user can edit the branch. if self.fresh: self.parent_id = qb.branch_hier[0][0] else: g.assurt((not self.parent_id) or (self.parent_id == qb.branch_hier[1][0])) # The user is allowed to set self.last_merge_rid only once: when the # branch is created. Once the branch exists, the user can only change # last_merge_rid by performing an update. # 2013.04.04: And remember to use rid_new and not rid_max. if self.last_merge_rid is None: self.last_merge_rid = qb.item_mgr.rid_new else: # NO: Allow update: g.assurt(self.fresh and (self.version == 1)) if ((self.last_merge_rid < 0) or (self.last_merge_rid > qb.item_mgr.rid_new)): raise GWIS_Error('last_merge_rid out-of-bounds: %d' % (self.last_merge_rid,)) # We fetch coverage_area as SVG (which is what all the append_* fcns. # expect), but PostGIS has no SVG import function (weird, [lb] thinks). # 2014.04.27: Rather than just repeat what's in the database for the # prevision branch version (and maybe losing precision since # we fetched coverage_area using conf.db_fetch_precision), # we can recalculate it: see: commit.py calls # byway.Many.branch_coverage_area_update. restore_carea = self.coverage_area if self.coverage_area.startswith('M '): # There's no geometry.svg_polygon_to_xy like there's a svg_line_to_xy, # so we use the geometry-type agnostic gml.flat_to_xys rather than # just filling in the blanks in geometry.py and writing such a fcn. # And this is a little ugly: we have to close the polygon ring, and # we have to make it a multipolygon for our geometry translation. # (Instead of this dance, we could, e.g., # ST_AsEWKT(br.coverage_area) AS coverage_area_wkt.) # Note that we fetched coverage_area using conf.db_fetch_precision, # so we've lost precision, so the caller should make sure to call # byway.Many.branch_coverage_area_update if they care (commit does). coverage_pgon = gml.flat_to_xys(self.coverage_area) coverage_pgon.append(coverage_pgon[0]) self.coverage_area = geometry.xy_to_ewkt_polygon([coverage_pgon,], precision=conf.postgis_precision) self.save_insert(qb, One.item_type_table, One.psql_defns) # Restore the geometry to SVG (even though in practive save_core # is just used by commit, so the coverage_area is no longer needed, # and is about to be recomputed). self.coverage_area = restore_carea
def landmarks_polygons(step, qb): # idea: get polygons close to end node # Currently kind of useless because we don't have terrain names # get geometry for end point of this step pts = gml.flat_to_xys(step.geometry) node_geo = pts[0] if (step.forward): node_geo = pts[len(pts) - 1] sql_str = ( """ SELECT DISTINCT ON (gia.stack_id) gia.stack_id, gia.acl_grouping, gia.name, gf.geofeature_layer_id, gf.geometry AS geometry, ST_AsText(gf.geometry) AS geometry_wkt, ST_DISTANCE(gf.geometry, ST_SetSRID(ST_Point(%s, %s), %d)) AS dist, ST_AsSVG(ST_Scale(gf.geometry, 1, -1, 1), 0, %d) AS geometry_svg FROM group_item_access AS gia JOIN geofeature AS gf ON (gia.item_id = gf.system_id) WHERE gia.valid_until_rid = %d AND NOT gia.deleted AND NOT gia.reverted AND gia.branch_id = %d AND gia.item_type_id = %d AND ST_DISTANCE(gf.geometry, ST_SetSRID(ST_Point(%s, %s), %d)) < %d AND gia.group_id = %d AND gia.access_level_id <= %d ORDER BY gia.stack_id, gia.acl_grouping DESC """ % (node_geo[0], node_geo[1], conf.default_srid, conf.db_fetch_precision, conf.rid_inf, qb.branch_hier[0][0], Item_Type.TERRAIN, node_geo[0], node_geo[1], conf.default_srid, Landmark.max_search_distance, group.Many.public_group_id(qb.db), Access_Level.client,)) rows = qb.db.sql(sql_str) for row in rows: # TODO: If namy is none, get terrain type log.debug('terrain geometry: ' + str(row['geometry_svg'])) step.landmarks.append(Landmark(name=row['name'], item_id=row['stack_id'], type_id=Item_Type.TERRAIN, geometry=row['geometry_svg'], dist=row['dist'])) return
def get_point_index(b, point): points = gml.flat_to_xys(b.geometry_wkt) dist_best = float('inf') i_best = 0 for i in range(0, len(points)-1): dist = dist_point_line(point, points[i], points[i+1]) if ((dist < dist_best) and (dist is not None)): i_best = i dist_best = dist return i_best + 1
def landmarks_big_crossings(step, qb): # idea: get road of certain types (e.g. highways) that geometrically cross # this step # TODO: the intersection logic could be improved sql_str = (""" SELECT DISTINCT ON (gia.stack_id) gia.stack_id, gia.acl_grouping, gia.name FROM group_item_access AS gia JOIN geofeature AS gf ON (gia.item_id = gf.system_id) WHERE gia.valid_until_rid = %d AND NOT gia.deleted AND NOT gia.reverted AND gia.branch_id = %d AND gia.item_type_id = %d AND ST_Crosses(gf.geometry, '%s'::GEOMETRY) AND gf.geofeature_layer_id IN (%d, %d, %d) AND gia.group_id = %d AND gia.access_level_id <= %d ORDER BY gia.stack_id, gia.acl_grouping DESC """ % ( conf.rid_inf, qb.branch_hier[0][0], Item_Type.BYWAY, geometry.xy_to_ewkt_line(gml.flat_to_xys(step.geometry)), byway.Geofeature_Layer.Highway, byway.Geofeature_Layer.Expressway, byway.Geofeature_Layer.Major_Trail, group.Many.public_group_id(qb.db), Access_Level.client, )) rows = qb.db.sql(sql_str) # Checking a set for membership is faster than using a list. name_list = set() for row in rows: try: if (len(row['name']) > 0 and not row['name'] in name_list): step.landmarks.append( Landmark(name=row['name'], item_id=row['stack_id'], type_id=Item_Type.BYWAY)) name_list.add(row['name']) except TypeError: # row['name'] is None. pass return
def landmarks_pois(step, qb): # get geometry for end point of this step pts = gml.flat_to_xys(step.geometry) node_geo = pts[0] if (step.forward): node_geo = pts[len(pts) - 1] sql_str = (""" SELECT DISTINCT ON (gia.stack_id) gia.stack_id, gia.acl_grouping, gia.name, gf.geometry AS geometry, ST_AsText(gf.geometry) AS geometry_wkt, ST_DISTANCE(gf.geometry, ST_SetSRID(ST_Point(%s, %s), %d)) as dist FROM group_item_access AS gia JOIN geofeature AS gf ON (gia.item_id = gf.system_id) WHERE gia.valid_until_rid = %d AND NOT gia.deleted AND NOT gia.reverted AND gia.branch_id = %d AND gia.item_type_id = %d AND ST_DISTANCE(gf.geometry, ST_SetSRID(ST_Point(%s, %s), %d)) < %d AND gia.group_id = %d AND gia.access_level_id <= %d ORDER BY gia.stack_id, gia.acl_grouping DESC """ % ( node_geo[0], node_geo[1], conf.default_srid, conf.rid_inf, qb.branch_hier[0][0], Item_Type.WAYPOINT, node_geo[0], node_geo[1], conf.default_srid, Landmark.max_search_distance, group.Many.public_group_id(qb.db), Access_Level.client, )) #gia.group_id IN (%s) => qb.filters.gia_use_gids? rows = qb.db.sql(sql_str) for row in rows: step.landmarks.append( Landmark(name=row['name'], item_id=row['stack_id'], type_id=Item_Type.WAYPOINT, geometry=row['geometry_wkt'], dist=row['dist']))
def landmarks_big_crossings(step, qb): # idea: get road of certain types (e.g. highways) that geometrically cross # this step # TODO: the intersection logic could be improved sql_str = ( """ SELECT DISTINCT ON (gia.stack_id) gia.stack_id, gia.acl_grouping, gia.name FROM group_item_access AS gia JOIN geofeature AS gf ON (gia.item_id = gf.system_id) WHERE gia.valid_until_rid = %d AND NOT gia.deleted AND NOT gia.reverted AND gia.branch_id = %d AND gia.item_type_id = %d AND ST_Crosses(gf.geometry, '%s'::GEOMETRY) AND gf.geofeature_layer_id IN (%d, %d, %d) AND gia.group_id = %d AND gia.access_level_id <= %d ORDER BY gia.stack_id, gia.acl_grouping DESC """ % (conf.rid_inf, qb.branch_hier[0][0], Item_Type.BYWAY, geometry.xy_to_ewkt_line(gml.flat_to_xys(step.geometry)), byway.Geofeature_Layer.Highway, byway.Geofeature_Layer.Expressway, byway.Geofeature_Layer.Major_Trail, group.Many.public_group_id(qb.db), Access_Level.client,)) rows = qb.db.sql(sql_str) # Checking a set for membership is faster than using a list. name_list = set() for row in rows: try: if (len(row['name']) > 0 and not row['name'] in name_list): step.landmarks.append(Landmark(name=row['name'], item_id=row['stack_id'], type_id=Item_Type.BYWAY)) name_list.add(row['name']) except TypeError: # row['name'] is None. pass return
def landmarks_pois(step, qb): # get geometry for end point of this step pts = gml.flat_to_xys(step.geometry) node_geo = pts[0] if (step.forward): node_geo = pts[len(pts) - 1] sql_str = ( """ SELECT DISTINCT ON (gia.stack_id) gia.stack_id, gia.acl_grouping, gia.name, gf.geometry AS geometry, ST_AsText(gf.geometry) AS geometry_wkt, ST_DISTANCE(gf.geometry, ST_SetSRID(ST_Point(%s, %s), %d)) as dist FROM group_item_access AS gia JOIN geofeature AS gf ON (gia.item_id = gf.system_id) WHERE gia.valid_until_rid = %d AND NOT gia.deleted AND NOT gia.reverted AND gia.branch_id = %d AND gia.item_type_id = %d AND ST_DISTANCE(gf.geometry, ST_SetSRID(ST_Point(%s, %s), %d)) < %d AND gia.group_id = %d AND gia.access_level_id <= %d ORDER BY gia.stack_id, gia.acl_grouping DESC """ % (node_geo[0], node_geo[1], conf.default_srid, conf.rid_inf, qb.branch_hier[0][0], Item_Type.WAYPOINT, node_geo[0], node_geo[1], conf.default_srid, Landmark.max_search_distance, group.Many.public_group_id(qb.db), Access_Level.client,)) #gia.group_id IN (%s) => qb.filters.gia_use_gids? rows = qb.db.sql(sql_str) for row in rows: step.landmarks.append(Landmark(name=row['name'], item_id=row['stack_id'], type_id=Item_Type.WAYPOINT, geometry=row['geometry_wkt'], dist=row['dist']))
def split_block(self, b, intersection, new_node_id=None): points = gml.flat_to_xys(b.geometry_wkt) index = get_point_index(b, intersection) # Insert the new vertex into the byway points.insert(index, intersection) # Split the byway at the new vertex new_split_block = copy.deepcopy(b) new_split_block.stack_id = self.get_fresh_id() new_split_block.split_from_stack_id = b.stack_id geom1 = list() geom2 = list() # TODO: this can be done without a for loop for i in range(0,len(points)): if (i <= index): geom1.append(points[i]) if (i >= index): geom2.append(points[i]) if (new_node_id is None): new_split_block.nid2 = self.get_fresh_id() else: new_split_block.nid2 = new_node_id b.nid1 = new_split_block.nid2 # Remove the old split block since its geometry has changed for block in self.new_byways: if block.stack_id == b.stack_id: self.new_byways.remove(block) break # Save the new split blocks to the byways list new_split_block.geometry_wkt = ( 'LINESTRING(%s)' % (gml.wkt_coords_format(geom1),)) self.new_byways.append(new_split_block) b.geometry_wkt = ( 'LINESTRING(%s)' % (gml.wkt_coords_format(geom2),)) self.new_byways.append(b) return b.nid1, new_split_block.stack_id, b.stack_id
def proj_block(self, point, block): geom_str = ("ST_GeomFromText('%s', %d)" % (block.geometry_wkt, conf.default_srid,)) rows = self.qb.db.sql( """ SELECT ST_AsEWKT( ST_Line_Interpolate_Point( %s, ST_Line_Locate_Point( %s, ST_SetSRID(ST_Point(%s, %s), %d)))) AS geom """ % (geom_str, geom_str, point[0], point[1], conf.default_srid,)) point = geometry.wkt_line_to_xy(rows[0]['geom'])[0] # Get block geometry block_points = gml.flat_to_xys(block.geometry_wkt) return point
def save_rstep(self, qb, route, step_number): self.route_id = route.system_id # FIXME: Do we need these? They're in the table... self.route_stack_id = route.stack_id self.route_version = route.version self.step_number = step_number g.assurt(self.byway_stack_id > 0) g.assurt(self.byway_version > 0) # 2014.08.19: flashclient sending byway_id="0" ??? if (not self.byway_id) or (self.byway_id <= 0): if route.stack_id not in One.warned_re_sysid_stk_ids: One.warned_re_sysid_stk_ids.add(route.stack_id) log.warning( 'save_rstep: byway_id not sent from client: route sid: %s' % (route.stack_id, )) branch_ids = [str(branch_tup[0]) for branch_tup in qb.branch_hier] # FIXME: The ORDER BY is pretty lame... but if we just have a stack ID # and a version, and if we don't check against the time when # the route was requested, we can't know for sure which branch # the byway is from (e.g., a route was requested in a leafy # branch and the parent branch's byways were selected, but # then one byway is edited in both the parent and the leaf, # then there are two byways with the same stack ID and version # but different branch_ids: since our qb is Current but the # route is historic, we would really need to find the revision # when the route was requested and use that to find the true # byway system_id). This code just finds the leafiest matching # byway... which is probably okay, since this code is for # saving byways, and most users will be saving to the basemap # branch (who uses the MetC Bikeways 2012 branch, anyway? No # one...), and also this code is just a stopgap until we fix # the real problem that is the client is not sending byway # system IDs, which might be a pyserver problem not sending # them to the client in the first place.... sys_id_sql = (""" SELECT iv.system_id FROM item_versioned AS iv WHERE iv.stack_id = %d AND iv.version = %d AND branch_id IN (%s) ORDER BY branch_id DESC LIMIT 1 """ % ( self.byway_stack_id, self.byway_version, ','.join(branch_ids), )) rows = qb.db.sql(sys_id_sql) g.assurt( len(rows) == 1) # Or not, if no match, which would be weird. self.byway_id = rows[0]['system_id'] g.assurt(self.byway_id > 0) if (self.geometry and (self.travel_mode == Travel_Mode.transit)): # Old CcpV1: db_glue automatically prepends the SRID for columns named # 'geometry' but we have to do it manually here so constraints pass. # 2012.09.27: What does 'constraints pass' mean? Doesn't db_glue fix # this list the comment says? # MAYBE: This feels like a gml fcn. See maybe: wkt_linestring_get. wkt_geom = ( 'SRID=%s;LINESTRING(%s)' % ( conf.default_srid, gml.wkt_coords_format( # FIXED?: Was: gml.flat_to_xys(self.geometry[2:]) gml.flat_to_xys(self.geometry)), )) else: # Either this is a byway-associated route_step, so we don't save the # byway's geometry (it's easy to lookup in the database), or something # else... wkt_geom = None if self.travel_mode == Travel_Mode.transit: # FIXME: Does this mean we're clearing existing geometry? Seems # weird... log.warning( 'save_rstep: transit step has geometry? are we clearing it?' ) # FIXME: What about wkt_geom? Is this right? self.transit_geometry = wkt_geom self.save_insert(qb, One.item_type_table, One.psql_defns)
def landmarks_graph_properties(step, qb): # idea: detect T intersections pts = gml.flat_to_xys(step.geometry) step_xys = (pts[0], pts[1]) node_id = step.beg_node_id if (step.forward): step_xys = (pts[len(pts)-1], pts[len(pts)-2]) node_id = step.fin_node_id # find byways connected to this node sql_str = ( """ SELECT DISTINCT ON (gia.stack_id) gia.stack_id, gia.acl_grouping, ST_AsText(gf.geometry) AS geometry_wkt, gf.beg_node_id FROM group_item_access AS gia JOIN geofeature AS gf ON (gia.item_id = gf.system_id) JOIN node_byway ON (node_byway.byway_stack_id = gia.stack_id) WHERE gia.valid_until_rid = %d AND NOT gia.deleted AND NOT gia.reverted AND gia.branch_id = %d AND gia.item_type_id = %d AND node_byway.branch_id = %d AND node_byway.node_stack_id = %d AND NOT gia.stack_id = %s AND gia.group_id = %d AND gia.access_level_id <= %d ORDER BY gia.stack_id, gia.acl_grouping DESC """ % (conf.rid_inf, qb.branch_hier[0][0], Item_Type.BYWAY, qb.branch_hier[0][0], node_id, step.byway_stack_id, group.Many.public_group_id(qb.db), Access_Level.client,)) rows = qb.db.sql(sql_str) # If not two, exit (no T intersection here) if not len(rows) == 2: return # get first two points of each byway byways_xys = list() for row in rows: xys = gml.flat_to_xys(row['geometry_wkt']) if (row['beg_node_id'] == node_id): byways_xys.append((xys[0], xys[1],)) else: byways_xys.append((xys[len(xys)-1], xys[len(xys)-2],)) # now we can calculate all angles, if the other two byways are almost # straight and they are both at least between 60 and 120 degrees from # this one angle1 = geometry.v_dir(step_xys[0], step_xys[1]) * 180 / math.pi angle2 = geometry.v_dir(byways_xys[0][0], byways_xys[0][1]) * 180 / math.pi angle3 = geometry.v_dir(byways_xys[1][0], byways_xys[1][1]) * 180 / math.pi # Check that agles are at 90+-30 degrees dif1 = angle1 - angle2 if not ((abs(dif1) > 60 and abs(dif1) < 120) or (abs(dif1) > 240 and abs(dif1) < 300)): return dif2 = angle1 - angle3 if not ((abs(dif2) > 60 and abs(dif2) < 120) or (abs(dif2) > 240 and abs(dif2) < 300)): return if (abs(dif2 - dif1) < 90): # angles are too close, probably perpendicular toward the same side return # We found a T intersection step.landmarks.append(Landmark(name='', item_id=-1, type_id=Item_Type.LANDMARK_T))
def continue_block(self, new_block, direction, last_block_ids=None, ignore=None): if direction == -1: endpoint_index = 0 else: endpoint_index = -1 extending_points = gml.flat_to_xys(new_block.geometry_wkt) if ignore is None: ignore = [new_block.stack_id] # If there are no blocks nearby, no need to extend this block nearest_block = self.get_nearest_block( extending_points[endpoint_index], last_block_ids, ignore=ignore) if (nearest_block is None or nearest_block.dist > self.wtem.distance_error): return None ignore_ids = [nearest_block.stack_id] near_points = gml.flat_to_xys(nearest_block.geometry_wkt) # If we are close to an endpoint of another block, connect to it. if (dist(extending_points[endpoint_index], near_points[0]) <= self.wtem.distance_error): # add blocks that connect to that endpoint to ignore list for next # block continuation ignore_ids.extend( [b.stack_id for b in self.get_connected_blocks( nearest_block, nearest_block.nid1)]) if direction == -1: extending_points[0] = near_points[0] new_block.nid1 = nearest_block.nid1 else: extending_points[-1] = near_points[0] new_block.nid2 = nearest_block.nid1 elif (dist(extending_points[endpoint_index], near_points[-1]) <= self.wtem.distance_error): # add blocks that connect to that endpoint to ignore list for next # block continuation ignore_ids.extend( [b.stack_id for b in self.get_connected_blocks( nearest_block, nearest_block.nid2)]) if direction == -1: extending_points[0] = near_points[-1] new_block.nid1 = nearest_block.nid2 else: extending_points[-1] = near_points[-1] new_block.nid2 = nearest_block.nid2 else: # Get location of closest point on the nearest block and the expected # index in that block's sequence of points. point = self.proj_block( extending_points[endpoint_index], nearest_block) new_node, b1_id, b2_id = self.split_block(nearest_block, point) ignore_ids = [b1_id, b2_id] # Connect the current block to the intersection if direction == -1: extending_points.insert(0, point) new_block.nid1 = new_node else: extending_points.append(point) new_block.nid2 = new_node new_block.geometry_wkt = ( 'LINESTRING(%s)' % (gml.wkt_coords_format(extending_points),)) return ignore_ids
def landmarks_polygons(step, qb): # idea: get polygons close to end node # Currently kind of useless because we don't have terrain names # get geometry for end point of this step pts = gml.flat_to_xys(step.geometry) node_geo = pts[0] if (step.forward): node_geo = pts[len(pts) - 1] sql_str = (""" SELECT DISTINCT ON (gia.stack_id) gia.stack_id, gia.acl_grouping, gia.name, gf.geofeature_layer_id, gf.geometry AS geometry, ST_AsText(gf.geometry) AS geometry_wkt, ST_DISTANCE(gf.geometry, ST_SetSRID(ST_Point(%s, %s), %d)) AS dist, ST_AsSVG(ST_Scale(gf.geometry, 1, -1, 1), 0, %d) AS geometry_svg FROM group_item_access AS gia JOIN geofeature AS gf ON (gia.item_id = gf.system_id) WHERE gia.valid_until_rid = %d AND NOT gia.deleted AND NOT gia.reverted AND gia.branch_id = %d AND gia.item_type_id = %d AND ST_DISTANCE(gf.geometry, ST_SetSRID(ST_Point(%s, %s), %d)) < %d AND gia.group_id = %d AND gia.access_level_id <= %d ORDER BY gia.stack_id, gia.acl_grouping DESC """ % ( node_geo[0], node_geo[1], conf.default_srid, conf.db_fetch_precision, conf.rid_inf, qb.branch_hier[0][0], Item_Type.TERRAIN, node_geo[0], node_geo[1], conf.default_srid, Landmark.max_search_distance, group.Many.public_group_id(qb.db), Access_Level.client, )) rows = qb.db.sql(sql_str) for row in rows: # TODO: If namy is none, get terrain type log.debug('terrain geometry: ' + str(row['geometry_svg'])) step.landmarks.append( Landmark(name=row['name'], item_id=row['stack_id'], type_id=Item_Type.TERRAIN, geometry=row['geometry_svg'], dist=row['dist'])) return
def landmarks_graph_properties(step, qb): # idea: detect T intersections pts = gml.flat_to_xys(step.geometry) step_xys = (pts[0], pts[1]) node_id = step.beg_node_id if (step.forward): step_xys = (pts[len(pts) - 1], pts[len(pts) - 2]) node_id = step.fin_node_id # find byways connected to this node sql_str = (""" SELECT DISTINCT ON (gia.stack_id) gia.stack_id, gia.acl_grouping, ST_AsText(gf.geometry) AS geometry_wkt, gf.beg_node_id FROM group_item_access AS gia JOIN geofeature AS gf ON (gia.item_id = gf.system_id) JOIN node_byway ON (node_byway.byway_stack_id = gia.stack_id) WHERE gia.valid_until_rid = %d AND NOT gia.deleted AND NOT gia.reverted AND gia.branch_id = %d AND gia.item_type_id = %d AND node_byway.branch_id = %d AND node_byway.node_stack_id = %d AND NOT gia.stack_id = %s AND gia.group_id = %d AND gia.access_level_id <= %d ORDER BY gia.stack_id, gia.acl_grouping DESC """ % ( conf.rid_inf, qb.branch_hier[0][0], Item_Type.BYWAY, qb.branch_hier[0][0], node_id, step.byway_stack_id, group.Many.public_group_id(qb.db), Access_Level.client, )) rows = qb.db.sql(sql_str) # If not two, exit (no T intersection here) if not len(rows) == 2: return # get first two points of each byway byways_xys = list() for row in rows: xys = gml.flat_to_xys(row['geometry_wkt']) if (row['beg_node_id'] == node_id): byways_xys.append(( xys[0], xys[1], )) else: byways_xys.append(( xys[len(xys) - 1], xys[len(xys) - 2], )) # now we can calculate all angles, if the other two byways are almost # straight and they are both at least between 60 and 120 degrees from # this one angle1 = geometry.v_dir(step_xys[0], step_xys[1]) * 180 / math.pi angle2 = geometry.v_dir(byways_xys[0][0], byways_xys[0][1]) * 180 / math.pi angle3 = geometry.v_dir(byways_xys[1][0], byways_xys[1][1]) * 180 / math.pi # Check that agles are at 90+-30 degrees dif1 = angle1 - angle2 if not ((abs(dif1) > 60 and abs(dif1) < 120) or (abs(dif1) > 240 and abs(dif1) < 300)): return dif2 = angle1 - angle3 if not ((abs(dif2) > 60 and abs(dif2) < 120) or (abs(dif2) > 240 and abs(dif2) < 300)): return if (abs(dif2 - dif1) < 90): # angles are too close, probably perpendicular toward the same side return # We found a T intersection step.landmarks.append( Landmark(name='', item_id=-1, type_id=Item_Type.LANDMARK_T))
def do_conflation(self): '''Viterbi algorithm''' now = time.time() self.stage_initialize('Conflating track') # Observations (track points) obs = [(float(tp.x), float(tp.y), tp.timestamp,) for tp in self.track.track_points] self.sanitize(obs) self.fetch_byways(obs) # List of dicts mapping an observation index and a byway step to the # composite probability that observation came from that byway V = [{}] # List of dicts mapping observation index and byway ID to the most likely # path (list of Steps) leading to that byway in the current iteration path = [{}] # List of lists giving the IDs of the byway visited at each step. visited_ids = [[]] # Get nearby blocks nearby_byways = self.get_nearby_blocks(obs[0]) # If there are no nearby blocks, create one. if (len(nearby_byways) == 0): bounds = self.new_block_bounds(obs, 0) block = self.create_new_block(obs, bounds) nearby_byways.append(block) # We use this to keep track of which is the current best result # (byway_meta, probability) max_result = [(None, 0,),] def initialize(blocks): # Initialize when t == 0 for b in blocks: # one for each direction (forward and backward) V[0]['f' + str(b.stack_id)] = self.emit_p(b, obs[0]) V[0]['b' + str(b.stack_id)] = self.emit_p(b, obs[0]) visited_ids[0].append(b.stack_id) path[0]['f' + str(b.stack_id)] = [Step(b, b.nid1, b.nid2),] path[0]['b' + str(b.stack_id)] = [Step(b, b.nid2, b.nid1),] max_result[0] = (b, V[0]['f' + str(b.stack_id)],) initialize(nearby_byways) # Run Viterbi for t > 0 progress = 0 t = 1 while t < len(obs): # Latest entry in V maps blocks to their probability of being the true # location of this observation V.append({}) path.append({}) visited_ids.append(list()) current_prog = math.floor(t * 100 / len(obs)) if (current_prog > progress): progress = current_prog self.stage_work_item_update(stage_progress=progress) # Get the blocks that could have produced this observation nearby_blocks = self.get_nearby_blocks(obs[t]) candidate_steps = list() # For each nearby block, add it to candidate_blocks if it is connected # to the end of any of the possible paths from the previous iteration for y in nearby_blocks: step = Step(y, y.nid1, y.nid2) for prev_id in V[t-1].keys(): if self.trans_p(path[t-1][prev_id][-1], step) > 0: candidate_steps.append(step) break step = Step(y, y.nid2, y.nid1) for prev_id in V[t-1].keys(): if self.trans_p(path[t-1][prev_id][-1], step) > 0: candidate_steps.append(step) break # If there are no nearby blocks, or all nearby blocks have a # transition probability of 0, create a new block and rewind Viterbi # to the first observation where the new block or connected blocks had # any emission probability if (len(candidate_steps) == 0): bounds = self.new_block_bounds(obs, t, visited_ids[t-1]) blocks = list() fixed = False if (bounds[0] == bounds[1]): main_ob = obs[bounds[0]] # all we need is to create an intersection nearby = self.get_nearby_blocks(main_ob, self.wtem.distance_error) # Check if there are endpoints of new blocks nearby, If so, # extend and connect for b in nearby: if (not b.stack_id in visited_ids[t-1]): points = gml.flat_to_xys(b.geometry_wkt) if (dist(main_ob, points[0]) < self.wtem.distance_error): self.continue_block(b, -1) fixed = True elif (dist(main_ob, points[-1]) < self.wtem.distance_error): self.continue_block(b, 1) fixed = True elif (self.create_intersection(b, main_ob)): fixed = True break else: continue # Remove the old split block since its geometry has changed for bwy in self.new_byways: if bwy.stack_id == b.stack_id: self.new_byways.remove(bwy) break # save modified block self.new_byways.append(b) blocks.extend(self.get_connected_blocks( self.get_nearest_block(main_ob,visited_ids[t-1]))) if (not fixed): new_block = self.create_new_block( obs, bounds, visited_ids[t-1]) blocks = self.get_connected_blocks(new_block) blocks.append(new_block) emission = False for index in range(0, len(obs)-1): for b in blocks: if (self.dist_ob_block(obs[index], b)[0] < self.wtem.cutoff_distance): emission = True break if (emission): break t = index # If rewinding to the beginning, reset the data structures if t > 0: del V[t:] del path[t:] del max_result[t:] del visited_ids[t:] else: V = [{}] path = [{}] max_result = [(None, 0,),] visited_ids = [[]] initialize(self.get_nearby_blocks(obs[0])) t = 1 continue fix_bool = True max_last = (None, 0,) # Iterate over all possible blocks for this observation for candidate in candidate_steps: direction = 'f' if (candidate.forward_node == candidate.bywaymeta.nid1): direction = 'b' candidate_id = direction + str(candidate.bywaymeta.stack_id) # Select the block that most probably would have preceded this # block and the probability # of that path (prob, state,) = max( [(V[t-1][prev_id] * self.trans_p(path[t-1][prev_id][-1], candidate) * self.emit_p(candidate.bywaymeta, obs[t]), prev_id,) for prev_id in V[t-1].keys()]) # Save the probability of this block if higher than 0 if (prob > 0): V[t][candidate_id] = prob # Keep track of the last really close blocks at this point if ((not candidate.bywaymeta.stack_id in visited_ids[t]) and (candidate.bywaymeta.dist <= self.wtem.distance_error)): visited_ids[t].append(candidate.bywaymeta.stack_id) # Save the most likely path leading to this block path[t][candidate_id] = path[t-1][state] + [candidate] if (prob > max_last[1]): max_last = (candidate_id, prob) if (prob > (10**-20)): fix_bool = False if (len(visited_ids[t]) == 0) and (t > 0): visited_ids[t] = visited_ids[t-1] if (max_last[1] > 0): max_result.append(max_last) else: max_result.append(max_result[t-1]) # If all values are too small, make bigger (does not affect # final results, which are used for comparison and not in an # absolute way) if fix_bool and (max_last[1] > 0): multiplier = round(1 / max_last[1]) for k in V[t].keys(): V[t][k] = V[t][k] * multiplier # Break if the algorithm did not find any possible blocks (should not # happen if we are adding new blocks) if (max_last[1] <= 0): break t += 1 log.debug('Finished Viterbi!') log.debug('Time for conflation: %s' % (str(time.time() - now),)) self.final_path = path[t-1][max_result[-1][0]] self.obs = obs self.stage_work_item_update(stage_progress=100)
def fetch_n_save(self): command.Op_Handler.fetch_n_save(self) if ((not self.req.client.username) or (self.req.client.username == conf.anonymous_username)): raise GWIS_Error('User must be logged in.') else: # 'erase' previous landmarks sql = ( """ UPDATE landmark_exp_landmarks SET current = '%s' WHERE username = %s AND route_system_id = %d """) % ('f', self.req.db.quoted(self.req.client.username), self.route_system_id,) self.req.db.transaction_begin_rw() self.req.db.sql(sql) self.req.db.transaction_commit() for l in self.landmarks: if l.geometry: xys = gml.flat_to_xys(l.geometry) if len(xys) > 2: l.geometry = "SRID=%s;LINESTRING(%s)" % (conf.default_srid, l.geometry,) else: l.geometry = "SRID=%s;POINT(%s)" % (conf.default_srid, l.geometry,) sql = """ INSERT INTO landmark_exp_landmarks (username, route_system_id, landmark_id, landmark_type_id, landmark_name, landmark_geo, step_number, created) VALUES (%s, %s, %s, %s, %s, %s, %s, now()) """ self.req.db.transaction_begin_rw() self.req.db.sql(sql, (self.req.db.quoted(self.req.client.username), self.route_system_id, l.item_id, l.type_id, l.name, l.geometry, l.step_number,)) self.req.db.transaction_commit() sql = ( """ UPDATE landmark_exp_route SET done = 't', last_modified = now() WHERE username = %s AND route_system_id = %d """) % (self.req.db.quoted(self.req.client.username), self.route_system_id,) self.req.db.transaction_begin_rw() self.req.db.sql(sql) self.req.db.transaction_commit()