Last active
May 5, 2025 21:21
-
-
Save grujdin/18e2c4546fce7c9c75d952589fe954eb to your computer and use it in GitHub Desktop.
Display geometry of each ADM2 Unit.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import os | |
| import xml.etree.ElementTree as ET | |
| import matplotlib.pyplot as plt | |
| import geopandas as gpd | |
| from shapely.geometry import Polygon | |
| from shapely.ops import unary_union | |
| def poslist_to_polygon(poslist_str): | |
| """ | |
| Convert a GML posList string to a Shapely Polygon. | |
| Returns a tuple (Polygon, number_of_points) or (None, n) if there aren't enough points. | |
| """ | |
| try: | |
| coords = poslist_str.strip().split() | |
| coord_pairs = [(float(coords[i]), float(coords[i + 1])) for i in range(0, len(coords), 2)] | |
| if len(coord_pairs) < 3: | |
| return None, len(coord_pairs) | |
| return Polygon(coord_pairs), len(coord_pairs) | |
| except Exception as e: | |
| print(f"⚠️ Error converting posList to Polygon: {e}") | |
| return None, 0 | |
| def save_adm2_composite_images(xml_file, output_folder, dpi=300, figsize=(16, 12)): | |
| """ | |
| Parses the XML file (assumed to be a GAUL level-2 file, e.g. g2015_2014_2.xml), | |
| groups features by their ADM2 code (and ADM2 name), and for each ADM2 unit creates a | |
| high-resolution composite map that displays all component polygons for that ADM2 unit. | |
| Each composite map is annotated (using the centroid of the merged geometry) with the ADM2 code | |
| and ADM2 name, and then saved using the ADM2 code as the filename. | |
| """ | |
| # Define namespaces – adjust if necessary | |
| namespaces = { | |
| 'gml': 'http://www.opengis.net/gml', | |
| 'gaul': 'http://www.fao.org/tempref/AG/Reserved/PPLPF/ftpOUT/GLiPHA/Gaulmaps/gaul_2008/documentation/GAUL%20Doc01%20Ver16.pdf', | |
| 'wfs': 'http://www.opengis.net/wfs', | |
| } | |
| # Parse the XML file | |
| tree = ET.parse(xml_file) | |
| root = tree.getroot() | |
| # Create output folder if needed | |
| if not os.path.exists(output_folder): | |
| os.makedirs(output_folder) | |
| print(f"🔹 Saving composite images in: {output_folder}") | |
| # Group features by ADM2 code. | |
| # For each feature (tag gaul:g2015_2014_2), extract: | |
| # ADM2 code and ADM2 name from gaul:adm2_code and gaul:adm2_name, | |
| # and the polygon geometry. | |
| adm2_groups = {} # key: adm2_code; value: dict with 'adm2_name' and list of polygons. | |
| for feature in root.findall('.//gaul:g2015_2014_2', namespaces): | |
| adm2_code_el = feature.find('gaul:adm2_code', namespaces) | |
| adm2_name_el = feature.find('gaul:adm2_name', namespaces) | |
| if adm2_code_el is None: | |
| continue | |
| adm2_code = adm2_code_el.text.strip() | |
| adm2_name = adm2_name_el.text.strip() if adm2_name_el is not None else "Unknown_ADM2" | |
| polygon_elements = feature.findall('.//gml:Polygon', namespaces) | |
| for polygon_element in polygon_elements: | |
| posList_el = polygon_element.find('.//gml:posList', namespaces) | |
| if posList_el is None or not posList_el.text.strip(): | |
| continue | |
| polygon, n_points = poslist_to_polygon(posList_el.text) | |
| if polygon is None: | |
| continue | |
| if adm2_code not in adm2_groups: | |
| adm2_groups[adm2_code] = {'adm2_name': adm2_name, 'polygons': []} | |
| adm2_groups[adm2_code]['polygons'].append(polygon) | |
| # For each ADM2 unit, create a composite map. | |
| for adm2_code, group in adm2_groups.items(): | |
| polygons = group['polygons'] | |
| if not polygons: | |
| print(f"⚠️ No valid polygons found for ADM2 {adm2_code}. Skipping...") | |
| continue | |
| # If there is more than one polygon, union them. | |
| if len(polygons) == 1: | |
| merged = polygons[0] | |
| else: | |
| # Fix invalid geometries with buffer(0) before unioning. | |
| fixed_polys = [poly if poly.is_valid else poly.buffer(0) for poly in polygons] | |
| try: | |
| merged = unary_union(fixed_polys) | |
| except Exception as e: | |
| print(f"⚠️ Error unioning polygons for ADM2 {adm2_code}: {e}") | |
| merged = fixed_polys[0] | |
| # Create a GeoDataFrame for the unioned polygon. | |
| gdf = gpd.GeoDataFrame({'geometry': [merged]}) | |
| # Create a high-resolution composite map. | |
| fig, ax = plt.subplots(figsize=figsize) | |
| gdf.plot(ax=ax, color='lightblue', edgecolor='black', alpha=0.7) | |
| # Annotate the composite map using the centroid of the merged polygon. | |
| centroid = merged.centroid | |
| ax.annotate(f"{adm2_code}\n{group['adm2_name']}", (centroid.x, centroid.y), | |
| fontsize=12, color='red', ha='center') | |
| ax.set_title(f"Composite Map for ADM2: {adm2_code} - {group['adm2_name']}", fontsize=16) | |
| ax.set_xlabel("Longitude", fontsize=14) | |
| ax.set_ylabel("Latitude", fontsize=14) | |
| output_path = os.path.join(output_folder, f"{adm2_code}.png") | |
| plt.savefig(output_path, dpi=dpi) | |
| plt.close(fig) | |
| print(f"🖼️ Saved composite image for ADM2 {adm2_code}: {output_path}") | |
| # Example usage: | |
| xml_file = 'D:/ProjDB/GAUL/g2015_2014_2.xml' # Path to your GAUL XML file (ADM2-level data) | |
| output_folder = 'D:/ProjDB/GAUL/ADM2_Composite_Images' # Folder to store composite images by ADM2 | |
| save_fid_composite_images = save_adm2_composite_images # Rename for clarity if desired | |
| save_adm2_composite_images(xml_file, output_folder) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment