Update threaded script, try to reduce multipolygons in geojson
This commit is contained in:
		
							parent
							
								
									693c40302d
								
							
						
					
					
						commit
						a1521f4a47
					
				
							
								
								
									
										95
									
								
								shp-to-geojson.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										95
									
								
								shp-to-geojson.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,95 @@
 | 
			
		||||
import geopandas as gpd
 | 
			
		||||
from shapely.geometry import LineString, MultiLineString, Polygon, MultiPolygon
 | 
			
		||||
import pandas as pd
 | 
			
		||||
 | 
			
		||||
df = gpd.read_file('original data/Sumter/RoadCenterlines_041125.shp.zip') #, crs=2237 NAD83 / Florida West (ftUS)
 | 
			
		||||
df = df.to_crs(4326) # Convert to WGS 84
 | 
			
		||||
 | 
			
		||||
def convert_to_linestrings(geom):
 | 
			
		||||
    """Convert any geometry to a list of LineStrings"""
 | 
			
		||||
    linestrings = []
 | 
			
		||||
    
 | 
			
		||||
    if geom is None or geom.is_empty:
 | 
			
		||||
        return []
 | 
			
		||||
    
 | 
			
		||||
    # If it's already a LineString, just return it
 | 
			
		||||
    if isinstance(geom, LineString):
 | 
			
		||||
        linestrings.append(geom)
 | 
			
		||||
    
 | 
			
		||||
    # If it's a MultiLineString, extract all LineStrings
 | 
			
		||||
    elif isinstance(geom, MultiLineString):
 | 
			
		||||
        for line in geom.geoms:
 | 
			
		||||
            linestrings.append(line)
 | 
			
		||||
    
 | 
			
		||||
    # If it's a Polygon, convert boundary to LineString(s)
 | 
			
		||||
    elif isinstance(geom, Polygon):
 | 
			
		||||
        # Exterior boundary
 | 
			
		||||
        if not geom.exterior.is_empty:
 | 
			
		||||
            linestrings.append(LineString(geom.exterior.coords))
 | 
			
		||||
        # Interior boundaries (holes)
 | 
			
		||||
        for interior in geom.interiors:
 | 
			
		||||
            if not interior.is_empty:
 | 
			
		||||
                linestrings.append(LineString(interior.coords))
 | 
			
		||||
    
 | 
			
		||||
    # If it's a MultiPolygon, process each polygon
 | 
			
		||||
    elif isinstance(geom, MultiPolygon):
 | 
			
		||||
        for poly in geom.geoms:
 | 
			
		||||
            # Exterior boundary
 | 
			
		||||
            if not poly.exterior.is_empty:
 | 
			
		||||
                linestrings.append(LineString(poly.exterior.coords))
 | 
			
		||||
            # Interior boundaries (holes)
 | 
			
		||||
            for interior in poly.interiors:
 | 
			
		||||
                if not interior.is_empty:
 | 
			
		||||
                    linestrings.append(LineString(interior.coords))
 | 
			
		||||
    
 | 
			
		||||
    return linestrings
 | 
			
		||||
 | 
			
		||||
def explode_linestring(linestring):
 | 
			
		||||
    """Convert a LineString into individual segments"""
 | 
			
		||||
    if linestring is None or linestring.is_empty:
 | 
			
		||||
        return []
 | 
			
		||||
    
 | 
			
		||||
    coords = list(linestring.coords)
 | 
			
		||||
    segments = []
 | 
			
		||||
    
 | 
			
		||||
    for i in range(len(coords) - 1):
 | 
			
		||||
        segment = LineString([coords[i], coords[i + 1]])
 | 
			
		||||
        segments.append(segment)
 | 
			
		||||
    
 | 
			
		||||
    return segments
 | 
			
		||||
 | 
			
		||||
# Convert all geometries to LineStrings first
 | 
			
		||||
df['linestrings'] = df.geometry.apply(convert_to_linestrings)
 | 
			
		||||
 | 
			
		||||
# Explode to get one row per LineString
 | 
			
		||||
df_exploded = df.explode('linestrings')
 | 
			
		||||
 | 
			
		||||
# Filter out rows with empty linestrings
 | 
			
		||||
df_exploded = df_exploded[df_exploded['linestrings'].notna()]
 | 
			
		||||
df_exploded = df_exploded[~df_exploded['linestrings'].apply(lambda x: x.is_empty if x is not None else True)]
 | 
			
		||||
 | 
			
		||||
# Now explode each LineString into segments
 | 
			
		||||
df_exploded['segments'] = df_exploded['linestrings'].apply(explode_linestring)
 | 
			
		||||
 | 
			
		||||
# Explode segments
 | 
			
		||||
df_final = df_exploded.explode('segments')
 | 
			
		||||
 | 
			
		||||
# Filter out empty segments
 | 
			
		||||
df_final = df_final[df_final['segments'].notna()]
 | 
			
		||||
df_final = df_final[~df_final['segments'].apply(lambda x: x.is_empty if x is not None else True)]
 | 
			
		||||
 | 
			
		||||
# Create final GeoDataFrame with segments as geometry
 | 
			
		||||
dfline = gpd.GeoDataFrame(
 | 
			
		||||
    data=df_final.drop(['geometry', 'linestrings', 'segments'], axis=1), 
 | 
			
		||||
    geometry=df_final['segments'],
 | 
			
		||||
    crs=df.crs
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
# Reset index
 | 
			
		||||
dfline = dfline.reset_index(drop=True)
 | 
			
		||||
 | 
			
		||||
# Save to GeoJSON
 | 
			
		||||
dfline.to_file('original data/Sumter/RoadCenterlines_041125.geojson', driver='GeoJSON')
 | 
			
		||||
 | 
			
		||||
print(f"Converted {len(df)} original features to {len(dfline)} line segments")
 | 
			
		||||
print(f"Geometry types in output: {dfline.geometry.geom_type.value_counts()}")
 | 
			
		||||
							
								
								
									
										12
									
								
								threaded.py
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								threaded.py
									
									
									
									
									
								
							@ -32,6 +32,13 @@ qgisfunctions = importlib.import_module("qgis-functions")
 | 
			
		||||
# Suppress warnings for cleaner output
 | 
			
		||||
warnings.filterwarnings('ignore')
 | 
			
		||||
 | 
			
		||||
import re
 | 
			
		||||
def titlecase(s):
 | 
			
		||||
    return re.sub(
 | 
			
		||||
        r"[A-Za-z]+('[A-Za-z]+)?",
 | 
			
		||||
        lambda word: word.group(0).capitalize(),
 | 
			
		||||
        s)
 | 
			
		||||
 | 
			
		||||
class RoadComparator:
 | 
			
		||||
    def __init__(self, tolerance_feet: float = 50.0, min_gap_length_feet: float = 100.0, 
 | 
			
		||||
                 n_jobs: int = None, chunk_size: int = 1000):
 | 
			
		||||
@ -308,11 +315,9 @@ class RoadComparator:
 | 
			
		||||
                            'surface': 'asphalt'
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        print("Added: "+properties['name'])
 | 
			
		||||
 | 
			
		||||
                        for key, value in original_properties.items():
 | 
			
		||||
                            if key == 'NAME':
 | 
			
		||||
                                properties['name'] = qgisfunctions.formatstreet(str(value)).title() if value is not None else None
 | 
			
		||||
                                properties['name'] = titlecase(qgisfunctions.formatstreet(value,None,None)) if value is not None else None
 | 
			
		||||
                            elif key == 'SpeedLimit':
 | 
			
		||||
                                properties['maxspeed'] = f"{value} mph" if value is not None else None
 | 
			
		||||
                            elif key == 'RoadClass':
 | 
			
		||||
@ -333,6 +338,7 @@ class RoadComparator:
 | 
			
		||||
                        })
 | 
			
		||||
                        
 | 
			
		||||
            except Exception as e:
 | 
			
		||||
                print(e)
 | 
			
		||||
                continue  # Skip problematic geometries
 | 
			
		||||
        
 | 
			
		||||
        return added_roads
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user