From 88c5a85a3e0e350ac6dcdc4cfa62e60cd796f038 Mon Sep 17 00:00:00 2001 From: Kelly-Ann Dolor Date: Wed, 30 Apr 2025 00:47:48 -0700 Subject: [PATCH 01/31] [DOCS] adding blog post, fixing blog post formatting, adding blog options --- docs/blog/.authors.yml | 11 + docs/blog/index.md | 4 + .../posts/spatial-tables-data-lakehouses.md | 543 ++++++++++++++++++ mkdocs.yml | 9 + 4 files changed, 567 insertions(+) create mode 100644 docs/blog/posts/spatial-tables-data-lakehouses.md diff --git a/docs/blog/.authors.yml b/docs/blog/.authors.yml index 425cddcec42..c1d2ef1291e 100644 --- a/docs/blog/.authors.yml +++ b/docs/blog/.authors.yml @@ -1,3 +1,4 @@ +<<<<<<< HEAD # Licensed to the Apache Software Foundation (ASF) under one # or more contributor license agreements. See the NOTICE file # distributed with this work for additional information @@ -15,10 +16,13 @@ # specific language governing permissions and limitations # under the License. +======= +>>>>>>> 97cd25dc74 ([DOCS] adding blog post, fixing blog post formatting, adding blog options) authors: kelly: name: Kelly-Ann Dolor description: Staff Technical Writer, Apache Sedona +<<<<<<< HEAD avatar: https://media.licdn.com/dms/image/v2/C4D03AQHKr_fPpGdNGw/profile-displayphoto-shrink_400_400/profile-displayphoto-shrink_400_400/0/1516866211726?e=2147483647&v=beta&t=p4PcyFNjZhvAIX8e1qZt4i3HbH5yOIXuEs8MTNoYZ3Y matt_powers: name: Matthew Powers @@ -28,3 +32,10 @@ authors: name: Matthew Forrest description: Director of Customer Engineering & Product Led Growth, Wherobots avatar: https://media.licdn.com/dms/image/v2/D4E03AQHxYTrEgc53_g/profile-displayphoto-shrink_200_200/profile-displayphoto-shrink_200_200/0/1722352936567?e=2147483647&v=beta&t=X10Z02O2UX8IRmbypcw-m-jbIDeNsPWWL-YOPX_v1XQ +======= + avatar: https://static.licdn.com/aero-v1/sc/h/9c8pery4andzj6ohjkjp54ma2 + matt_powers: + name: Matthew Powers + description: Staff Developer Relations Engineer, Apache Sedona + avatar: https://media.licdn.com/dms/image/v2/C4E03AQHL3oztZlTr2w/profile-displayphoto-shrink_200_200/profile-displayphoto-shrink_200_200/0/1517751981945?e=2147483647&v=beta&t=66hsE-PF25_Uc1EbjnljUOmqjl3NwJ0lHAcZkusxnO0 +>>>>>>> 97cd25dc74 ([DOCS] adding blog post, fixing blog post formatting, adding blog options) diff --git a/docs/blog/index.md b/docs/blog/index.md index 90a1400c6fe..c76f6bac294 100644 --- a/docs/blog/index.md +++ b/docs/blog/index.md @@ -1,3 +1,4 @@ +<<<<<<< HEAD --- hide: @@ -26,3 +27,6 @@ title: Blog --> The official source for Apache Sedona news, technical insights, release updates, and best practices in large-scale spatial data management. +======= +# Apache Sedona Blog +>>>>>>> 97cd25dc74 ([DOCS] adding blog post, fixing blog post formatting, adding blog options) diff --git a/docs/blog/posts/spatial-tables-data-lakehouses.md b/docs/blog/posts/spatial-tables-data-lakehouses.md new file mode 100644 index 00000000000..d665437ccb7 --- /dev/null +++ b/docs/blog/posts/spatial-tables-data-lakehouses.md @@ -0,0 +1,543 @@ +--- +date: + created: 2025-04-30 +authors: + - matt_powers + - kelly +--- + +# Geospatial Data on Iceberg: The Lakehouse Advantage + +This post delves into the benefits of Lakehouse architecture for spatial tables +and differentiate its approach from standard data warehouses and data lakes. + +While spatial data requires different types of metadata and optimizations, +it doesn't require entirely different file formats. + +Recent advancements, specifically the addition of native geometry/geography types to +Apache Parquet and the Apache Iceberg V3 specification, now enable the spatial data community +to fully integrate with and take advantage of Lakehouse architectures, overcoming previous +ecosystem fragmentation. + + +Many of the benefits that Lakehouses provide for tabular data also apply to spatial data, including: + +* **Versioned data:** Lakehouses automatically track changes to spatial features over time, creating distinct versions ideal for historical analysis and auditing how geometries or attributes evolved. +* **Time travel:** This capability allows querying spatial data (like features, boundaries, or locations) exactly as that data existed at a specific prior time or version, crucial for reproducibility and auditing historical spatial relationships. +* **Schema enforcement:** Lakehouses enforce schemas to ensure spatial data consistency by guaranteeing correct geometry types and attribute formats, which improve data quality and query reliability. +* **Database Optimizations:** Techniques like geographic partitioning, data skipping using bounding boxes, and columnar storage accelerate spatial queries and improve storage efficiency within the Lakehouse. + +## Data Lakehouse Architecture Overview + +Lakehouse architectures use open table formats like Apache Iceberg, Delta Lake, or Hudi to manage data stored on underlying platforms like cloud object storage (e.g. Amazon S3, or Google Cloud Storage). + +Tables in Lakehouses are governed by a catalog. These catalogs don't store files–files are stored in cloud object storage. Rather, catalogs maintain records of related metadata information, like names of available databases/tables, table schemas, historical information needed for features like time travel. The catalog enables tools like Apache Spark, Trino, and Apache Flink, to know the location of your table or its latest state–enabling accurate database queries and analysis. + +Example Lakehouse catalogs include Databricks' Unity catalog and the open source Apache Polaris. + + The catalogs allow for role-based access control (RBAC) at the tabular level and features like multi-table transactions, which ensure that relevant changes to dependent tables are applied atomically and without manual intervention. + +You can query tables in the Lakehouse Architecture for business intelligence (BI) reporting, data science, machine learning, and other complex analyses. + +![][image1] + +The Lakehouse Architecture offers several advantages: + +* Data is stored in open formats so any engine can query it, and there is no vendor lock-in. +* Lakehouses support all the features familiar to data warehouses, like reliable transactions, Data Manipulation Language (DML) operations, and RBAC . +* Lakehouses are performant enough for low-latency applications like BI dashboards. +* Lakehouses are interoperable with proprietary tools like BigQuery, Redshift, or Esri. +* You can store Lakehouses in cloud-based storage systems without any additional charges. +* Lakehouses are compatible with any engine. You can use one engine for ingestion, another for ETL, and a third for Machine Learning. The architecture encourages using the best engine for the job. + +## Lakehouses & spatial data + +Earlier, we mentioned 2 important features of lakehouses: Multi-table transactions and RBAC. Let's delve into how these 2 features can be beneficial for working with spatial data. + +### Multi-table transactions + +**Scenario:** Imagine a retail company is closing a store in New Jersey. Let's assume that this company maintains 3 different tables: `stores` (containing point geometry), `sales_territories` (containing polygon geometry), and `sales_performance` (containing no geometry). + +These tables are interdependent: The `store_id` field is in `stores` and `sales_territories` and `territory_id` is in `sales_territories` and `sales_performance`. + +The `stores` table is indirectly linked to `sales_performance` via the `sales_territories` table. + +**Impact:** With Multi-table transactions, the updates to the `stores`, `sales_territories`, and `sales_performance` tables are bundled together. This ensures that all of these related changes (store status, territory boundaries/assignments, sales targets) are completed successfully and become visible at the same time, maintaining accurate and consistent operational data for sales management and analysis. + +Without MTT, if the territory updates fail after the store is marked closed, the company might have inconsistent data showing a closed store still linked to its old territory, or incorrect sales targets. + +Let's see how Lakehouses differ from data lakes. + +## Lakehouses vs. Data Lakes + +In contrast, Data Lakes store data in files without a metadata layer, so they don't guarantee reliable transactions. + +The following are a few examples of data lakes: + +* GeoParquet files stored in AWS S3 +* GeoJSON files stored in Azure Blob Storage +* CSV files with WKT geometry data stored in GCP + +Generally, data lakes lack built-in mechanisms to coordinate atomic changes across multiple files or objects. As a result of this and other architectural limitations, data lakes do not support reliable multi-table transactions. + +Consequently, traditional data lakes present challenges for common data tasks: they struggle to efficiently execute developer-centric operations like `DELETE` and `MERGE`; modifying datasets often requires downtime to maintain consistency during file rewrites; and they typically lack the sophisticated performance optimizations (like advanced indexing) found in more performant database systems. + +The Lakehouse metadata layer is relatively small, so the storage costs for a Lakehouse and a data lake are about the same. However, Lakehouses allow for better performance, so compute expenses can be generally lower than those of a data lake. + +## Lakehouses vs. Data Warehouses + +A Data Warehouse is an analytics system typically powered by a proprietary engine with similarly proprietary file formats. However, due to many modern customers wanting to avoid vendor-lock-in via a proprietary file format, data warehouses also began supporting Lakehouse Storage Systems in addition to proprietary file formats. + +Still, data warehouses generally exhibit the following limitations: + +* Pricing models frequently package storage and compute, requiring users to pay for more compute even if they only need more storage. +* Storing data in proprietary file formats limits compatibility with other engines. +* Querying data stored in open file formats **can result** in slower performance compared to proprietary formats. Performance can suffer in shared compute environments when resource-intensive queries from one user impact others. + +Many modern enterprises prefer the Lakehouse architecture because it's vendor-neutral, low-cost, and open–compatible with any engine that builds a connector. + +In the next section, we'll discuss how to create tables with Iceberg. + +## Creating tables with Iceberg + +The following code sample demonstrates how to create and populate an Iceberg table within a Lakehouse. In this example, we'll create a customers table with id and first_name columns: + +```py +CREATE TABLE local.db.customers (id string, first_name string) +USING iceberg +TBLPROPERTIES('format-version'='3'); +``` + +Next, let's append some data to the table: + +```py +df = sedona.createDataFrame([ + ("a", "Bob"), + ("b", "Mary"), + ("c", "Sue"), +], ["id", "first_name"]) + +df.write.format("iceberg").mode("append").saveAsTable("local.db.customers") +``` + +Finally, run a query on the `customers` table: + +sedona.table("local.db.customers").show() + +```py ++---+----------+ +| id|first_name| ++---+----------+ +| a| Bob| +| b| Mary| +| c| Sue| ++---+----------+ +``` + +Creating a table with tabular data is straightforward. Now let's see how to make a table with spatial data in Iceberg. + +## Creating spatial tables with Iceberg v3 + +With Iceberg announcing native support for geospatial data, we can include spatial columns in tables without any special data accommodations. + +Let's create a customer_purchases table with a purchase_location column that contains Point Geometry of the different store locations + +```py +CREATE TABLE local.db.customer_purchases (id string, price double, geometry geometry) +USING iceberg +TBLPROPERTIES('format-version'='3'); +``` + +Now, let's append the location data to the table: + +```py +coords = [ + (-88.110352, 24.006326), + (-77.080078, 24.006326), + (-77.080078, 31.503629), + (-88.110352, 31.503629), + (-88.110352, 24.006326) +] +df = sedona.createDataFrame([ + ("a", 10.99, Polygon(coords)), + ("b", 3.5, Point(1, 2)), + ("c", 1.95, Point(3, 4)), +], ["id", "price", "geometry"]) + +df.write.format("iceberg").mode("append").saveAsTable("local.db.customer_purchases") +``` + +The spatial table uses `Point` geometries for exact purchase locations and `Polygon` geometries for purchases tied to an approximate region. + +## Joining an Iceberg tabular table with a spatial table + +Let's discuss how to join the customers and customer_purchases tables. + +```py +customers = sedona.table("local.db.customers") +purchases = sedona.table("local.db.customer_purchases") + +joined = customers.join(purchases, "id") +joined.show() + + ++---+----------+-----+--------------------+ +| id|first_name|price| geometry| ++---+----------+-----+--------------------+ +| a| Bob|10.99|POLYGON ((-88.110...| +| b| Mary| 3.5| POINT (1 2)| +| c| Sue| 1.95| POINT (3 4)| ++---+----------+-----+--------------------+ +``` + +Now, we can see the customer information and the location of their purchases all in one table. + +It's easy to join any tables with Sedona, regardless of the underlying file format, because Sedona has so many built-in file readers (e.g., you can easily join one table stored in Shapefiles and another stored in GeoParquet files). But it's even easier when Iceberg stores the tabular and spatial tables in the same catalog. + +## Optimizing spatial tables in Lakehouses + +!!!tip "Speed up your lakehouse queries" + To speed up your Lakehouse queries, you can co-locate similar data in the same files and eliminate excessively small files. + +Let's look at the following spatial table stored in GeoParquet. This table is the Overture Maps Foundation buildings dataset. + +```py +( + sedona + .table() + .withColumn("geometry", ST_GeomFromWKB(col("geometry"))) + .select("id", "geometry", "num_floors", "roof_color") + .createOrReplaceTempView("my_fun_view") +) +``` + +```py +import pyspark.sql.functions as sql_funcs + +# Assume 'sedona' is the configured SparkSession from Section 0 + +# Define the Overture Maps source path (condensed) +overture_release = "2025-03-19.0" # Define the specific release date +# Construct the full S3 path directly +overture_s3_path = f"s3://overturemaps-us-west-2/release/{overture_release}/theme=buildings/type=building" + +print(f"Reading Overture buildings data from: {overture_s3_path}") + +try: + # Read the GeoParquet data directly from S3 + buildings_df_raw = sedona.read.parquet(overture_s3_path) + + # Select ID, convert WKB geometry, and extract height from properties + buildings_df = ( + buildings_df_raw + .withColumn("geometry", sql_funcs.expr("ST_GeomFromWKB(geometry)")) + .withColumn("height", sql_funcs.col("properties.height").cast("double")) + .select("id", "geometry", "height") + .filter(sql_funcs.col("geometry").isNotNull()) + ) + + # Create a temporary view for querying the source data + source_view_name = "ov_buildings_source_view" + buildings_df.createOrReplaceTempView(source_view_name) + + # Confirmation message + print(f"Created temporary view '{source_view_name}'") + +except Exception as e: + # Keep the more detailed error reporting + print(f"ERROR: Could not read or process data from {overture_s3_path}") + print(f"Error details: {e}") + print("Please check the path, release date, network access, and cloud credentials/permissions.") + raise # Re-raise the exception to stop execution if needed +``` + +Let's run a filtering query on this GeoParquet dataset: + +```py +# Define the area of interest polygon (WKT format) +spot = "POLYGON((-82.258759 29.129371, -82.180481 29.136569, -82.202454 29.173747, -82.258759 29.129371))" + +# Construct the SQL query using the temporary view name +# Select relevant columns for the result +sql_query_source = f""" +SELECT id, height +FROM {source_view_name} +WHERE ST_Contains(ST_GeomFromWKT('{spot}'), geometry) +""" + +print(f"Running spatial query on source data view '{source_view_name}'...") + +try: + # Execute the query and get the count + source_results_df = sedona.sql(sql_query_source) + source_count = source_results_df.count() + print(f"Query on source GeoParquet data returned {source_count} results.") + # You can show some results if desired: + # source_results_df.show(10) + + # IMPORTANT: Performance claims are removed + # Actual performance depends heavily on many factors. + +except Exception as e: + print(f"ERROR: Failed to run query on source view '{source_view_name}'.") + print(f"Error details: {e}") + source_count = -1 # Indicate failure +``` + +Let's convert this dataset to Iceberg: + +```py +# Assume 'sedona', 'buildings_df', 'source_count', 'overture_release', +# and 'iceberg_db_name' (e.g., 'blog_db') exist from previous sections + +# Define the full Iceberg table name using the database configured in setup +iceberg_table_full_name = f"{iceberg_db_name}.overture_buildings_{overture_release.replace('-', '_').replace('.', '_')}" + +print(f"Preparing Iceberg conversion for: {iceberg_table_full_name}") + +iceberg_write_successful = False # Initialize status flag +# Check if source data is available before proceeding +if 'buildings_df' in locals() and source_count != -1: + # Define minimal Iceberg table DDL string + # Using format-version 2 for broad compatibility + sql_create_iceberg = f""" + CREATE TABLE IF NOT EXISTS {iceberg_table_full_name} (id STRING, geometry GEOMETRY, height DOUBLE) + USING iceberg PARTITIONED BY (bucket(16, id)) TBLPROPERTIES('format-version'='2'); + """ + try: + # Create the Iceberg table structure + sedona.sql(sql_create_iceberg) + + # Write the DataFrame (from Section 1) to the Iceberg table, overwriting if exists + (buildings_df + .select("id", "geometry", "height") # Ensure correct columns + .write + .format("iceberg") + .mode("overwrite") + .saveAsTable(iceberg_table_full_name) + ) + print(f"Successfully wrote data to Iceberg table: {iceberg_table_full_name}") + iceberg_write_successful = True # Update status on success + + except Exception as e: + print(f"ERROR during Iceberg create/write for {iceberg_table_full_name}: {e}") + # iceberg_write_successful remains False +else: + print("Skipping Iceberg conversion: Source data unavailable or prior errors.") + # iceberg_write_successful remains False + +# The 'iceberg_write_successful' flag indicates if the next step can proceed +``` + +Now, let's rerun the same query on the Iceberg table: + +```py +# Check if Iceberg write was successful before querying +if iceberg_write_successful: + + # Use the same polygon WKT as before + # spot = "POLYGON(...)" # Already defined in Section 2 + + # Construct the SQL query using the full Iceberg table name + sql_query_iceberg = f""" + SELECT id, height + FROM {iceberg_table_full_name} + WHERE ST_Contains(ST_GeomFromWKT('{spot}'), geometry) + """ + + print(f"Running spatial query on Iceberg table '{iceberg_table_full_name}'...") + + try: + # Execute the query and get the count + iceberg_results_df = sedona.sql(sql_query_iceberg) + iceberg_count = iceberg_results_df.count() + print(f"Query on Iceberg table returned {iceberg_count} results.") + # iceberg_results_df.show(10) + + # Compare counts if desired + if source_count >= 0: + print(f"(Count comparison: Source View={source_count}, Iceberg Table={iceberg_count})") + + except Exception as e: + print(f"ERROR: Failed to run query on Iceberg table '{iceberg_table_full_name}'.") + print(f"Error details: {e}") +else: + print("Skipping query on Iceberg table due to issues in previous steps.") + +``` + +## Spatial tables in Data lakes + +Let's compare these Iceberg Lakehouse tables with spatial tables built with data lakes. + +### Lack of Atomicity + +```py +# --- Define path for the traditional data lake "table" --- +data_lake_path = "/tmp/datalake_buildings_table" +print(f"Using traditional data lake path: {data_lake_path}") + +# Function to safely clean up directory +def cleanup_path(path): + if os.path.exists(path): + try: + shutil.rmtree(path) + print(f"Cleaned up directory: {path}") + except OSError as e: + print(f"Error cleaning up {path}: {e}") + +cleanup_path(data_lake_path) # Start clean +``` + +Operating directly on data lake file directories places the burden of state management on +the developer, as these operations generally lack atomicity. Ensuring predictable outcomes for writes +or overwrites might involve explicit state preparation (like clearing target directories), a step made redundant by Iceberg's built-in transactional consistency. + +### Reading the data + +Writing the code for basic read operations often involves a similar level of effort, whether you are targeting raw data lake files or tables within a data lakehouse. + +```py +# Write the initial data to the data lake path to simulate its existence +try: + print("Simulating initial data presence in data lake path...") + (buildings_df + # Convert geometry to WKB for standard Parquet storage + .withColumn("geometry_wkb", sql_funcs.expr("ST_AsWKB(geometry)")) + .select("id", "height", "geometry_wkb") + .write.format("parquet").save(data_lake_path) + ) + print(f"Initial data written to {data_lake_path}") + + # Create a temporary view on this data lake path + source_dl_view_name = "buildings_datalake_source_view" + (sedona.read.parquet(data_lake_path) + .withColumn("geometry", sql_funcs.expr("ST_GeomFromWKB(geometry_wkb)")) + .createOrReplaceTempView(source_dl_view_name) + ) + print(f"Created view '{source_dl_view_name}' on data lake path.") + + # Run the same spatial filter query + spot_wkt = "POLYGON((-82.258759 29.129371, -82.180481 29.136569, -82.202454 29.173747, -82.258759 29.129371))" + sql_query_source = f""" + SELECT id, height + FROM {source_dl_view_name} + WHERE ST_Contains(ST_GeomFromWKT('{spot_wkt}'), geometry) + """ + print(f"Running spatial query on '{source_dl_view_name}'...") + start_time = time.time() + source_dl_count = sedona.sql(sql_query_source).count() + end_time = time.time() + print(f"Query on data lake view returned {source_dl_count} results (took {end_time - start_time:.2f}s).") + +except Exception as e: + print(f"Error during initial read/query setup for data lake: {e}") + raise +``` + +### Updating the dataset + +This code simulates a common update pattern for data stored directly in +data lake files, in this case, adding an `is_in_spot` column based on a spatial +filter. The required process involves reading the entire existing dataset, applying +the modification to all records in memory, and then rewriting the whole modified +dataset back to storage, overwriting the original. + +This highlights key data +lake disadvantages: the inefficiency of the full read/rewrite cycle, and the +critical lack of atomicity in the overwrite step, which risks data corruption +or loss if the write operation fails partway through. + +```py +print(f"Simulating an UPDATE on the data lake table (via Read-Modify-Rewrite)...") +print(f"Goal: Add 'is_in_spot' column to data in {data_lake_path}") + +try: + # Read the *entire* dataset again + print("Reading entire dataset for modification...") + current_data_df = ( + sedona.read.parquet(data_lake_path) + .withColumn("geometry", sql_funcs.expr("ST_GeomFromWKB(geometry_wkb)")) + ) + read_count = current_data_df.cache().count() # Cache for potential reuse & count + print(f"Read {read_count} records.") + + # Add the new column + print("Adding 'is_in_spot' column...") + modified_data_df = current_data_df.withColumn( + "is_in_spot", + sql_funcs.expr(f"ST_Contains(ST_GeomFromWKT('{spot_wkt}'), geometry)") + ) + print(f"Rewriting ENTIRE dataset ({read_count} records) with new column back to {data_lake_path}...") + start_time_write = time.time() + (modified_data_df + .withColumn("geometry_wkb", sql_funcs.expr("ST_AsWKB(geometry)")) +# Convert back for storage + .select("id", "height", "geometry_wkb", "is_in_spot") + .write.format("parquet").mode("overwrite").save(data_lake_path) + ) + end_time_write = time.time() + print(f"Data lake overwrite complete (took {end_time_write - start_time_write:.2f}s).") + current_data_df.unpersist() + print("DISADVANTAGE 1 (Inefficiency): Update required full read & full rewrite.") + print("DISADVANTAGE 2 (No Atomicity): If the overwrite failed, data is lost/corrupted.") + +except Exception as e: + print(f"ERROR during data lake update (Read-Modify-Rewrite): {e}") + print(f"The directory {data_lake_path} is likely in an INCORRECT/CORRUPTED state!") + data_lake_update_failed = True +``` + +### Querying "updated" table + +This final step demonstrates querying the data lake path after the simulated +update (the burdensome overwrite). Since the raw directory of files doesn't +maintain a consistent, managed table state like Iceberg, querying the results +requires re-reading the potentially modified Parquet files, re-applying +transformations (like parsing the WKB geometry), and creating a new temporary +view. The original spatial filter query is then executed against this newly +created view. This entire process, necessary to access the latest state, is +contingent on the success of the previous non-atomic overwrite operation and +contrasts with the simpler, direct query against an already updated and +consistent Iceberg table. + +```py +print(f"Running spatial query on the overwritten data lake path '{data_lake_path}'...") + +if not 'data_lake_update_failed' in locals() or not data_lake_update_failed: + try: + final_dl_view_name = "buildings_datalake_final_view" + (sedona.read.parquet(data_lake_path) + .withColumn("geometry", sql_funcs.expr("ST_GeomFromWKB(geometry_wkb)")) + .createOrReplaceTempView(final_dl_view_name) + ) + + sql_query_final = f""" + SELECT id, height, is_in_spot -- Can now select the new column + FROM {final_dl_view_name} + WHERE ST_Contains(ST_GeomFromWKT('{spot_wkt}'), geometry) + """ + start_time_final = time.time() + final_dl_count = sedona.sql(sql_query_final).count() + end_time_final = time.time() + print(f"Query on final data lake view returned {final_dl_count} results (took {end_time_final - start_time_final:.2f}s).") + + except Exception as e: + print(f"ERROR running query on final data lake data: {e}") +else: + print("Skipping final query due to failure during update.") +``` + +## Conclusion + +Lakehouse architecture offers many advantages for the data community, and +with the native support native for `geometry` and `geography` (GEO) data types +in Iceberg, the spatial community can now take advantage of these benefits. +This marks a fundamental shift from simply storing spatial data *within* +eneric types (like binary WKB or string WKT). + +This native support is expected to improve interoperability and performance +across various query engines, including Sedona. + +Stay tuned for future updates regarding Apache Sedona's support for the +native `GEOMETRY` and `GEOGRAPHY` types introduced in Apache Iceberg v3. diff --git a/mkdocs.yml b/mkdocs.yml index f1029301861..d55aefa95f7 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -137,6 +137,7 @@ nav: - Aggregate Function (Snowflake): api/snowflake/vector-data/AggregateFunction.md - Predicate (Snowflake): api/snowflake/vector-data/Predicate.md - Blog: blog/index.md + - Community: - Community: community/contact.md - Contributor Guide: @@ -242,6 +243,7 @@ plugins: # prebuild_index: true - macros - blog: +<<<<<<< HEAD # Format for displaying the date of blog posts (e.g., "full" for full date format) post_date_format: full # Whether to include a table of contents (TOC) for blog posts @@ -250,6 +252,13 @@ plugins: archive_date_format: MMMM yyyy # Maximum number of authors to display in the post excerpt post_excerpt_max_authors: 5 +======= + post_date_format: full + blog_toc: true + archive_date_format: MMMM yyyy + post_excerpt_max_authors: 5 + +>>>>>>> 97cd25dc74 ([DOCS] adding blog post, fixing blog post formatting, adding blog options) - git-revision-date-localized: type: datetime - mike: From 71b16550796ad2159a0764e5c899f15abc233fcb Mon Sep 17 00:00:00 2001 From: Kelly-Ann Dolor Date: Wed, 30 Apr 2025 10:08:23 -0700 Subject: [PATCH 02/31] [DOCS] fixes --- docs/blog/posts/spatial-tables-data-lakehouses.md | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/docs/blog/posts/spatial-tables-data-lakehouses.md b/docs/blog/posts/spatial-tables-data-lakehouses.md index d665437ccb7..140f4923abf 100644 --- a/docs/blog/posts/spatial-tables-data-lakehouses.md +++ b/docs/blog/posts/spatial-tables-data-lakehouses.md @@ -2,8 +2,10 @@ date: created: 2025-04-30 authors: + - matt_powers - kelly + --- # Geospatial Data on Iceberg: The Lakehouse Advantage @@ -35,7 +37,7 @@ Tables in Lakehouses are governed by a catalog. These catalogs don't store files Example Lakehouse catalogs include Databricks' Unity catalog and the open source Apache Polaris. - The catalogs allow for role-based access control (RBAC) at the tabular level and features like multi-table transactions, which ensure that relevant changes to dependent tables are applied atomically and without manual intervention. + The catalogs allow for role-based access control (RBAC) at the tabular level and features like single-table transactions, which ensure that relevant changes to dependent tables are applied atomically and without manual intervention. You can query tables in the Lakehouse Architecture for business intelligence (BI) reporting, data science, machine learning, and other complex analyses. @@ -43,7 +45,7 @@ You can query tables in the Lakehouse Architecture for business intelligence (BI The Lakehouse Architecture offers several advantages: -* Data is stored in open formats so any engine can query it, and there is no vendor lock-in. +* Data is stored in open formats, letting any engine can query it, avoiding vendor lock-in. * Lakehouses support all the features familiar to data warehouses, like reliable transactions, Data Manipulation Language (DML) operations, and RBAC . * Lakehouses are performant enough for low-latency applications like BI dashboards. * Lakehouses are interoperable with proprietary tools like BigQuery, Redshift, or Esri. @@ -52,9 +54,9 @@ The Lakehouse Architecture offers several advantages: ## Lakehouses & spatial data -Earlier, we mentioned 2 important features of lakehouses: Multi-table transactions and RBAC. Let's delve into how these 2 features can be beneficial for working with spatial data. +Earlier, we mentioned 2 important features of lakehouses: Single-table transactions and RBAC. Let's delve into how these 2 features can be beneficial for working with spatial data. -### Multi-table transactions +### Single-table transactions **Scenario:** Imagine a retail company is closing a store in New Jersey. Let's assume that this company maintains 3 different tables: `stores` (containing point geometry), `sales_territories` (containing polygon geometry), and `sales_performance` (containing no geometry). @@ -62,7 +64,7 @@ These tables are interdependent: The `store_id` field is in `stores` and `sales_ The `stores` table is indirectly linked to `sales_performance` via the `sales_territories` table. -**Impact:** With Multi-table transactions, the updates to the `stores`, `sales_territories`, and `sales_performance` tables are bundled together. This ensures that all of these related changes (store status, territory boundaries/assignments, sales targets) are completed successfully and become visible at the same time, maintaining accurate and consistent operational data for sales management and analysis. +**Impact:** With Single-table transactions, the updates to the `stores`, `sales_territories`, and `sales_performance` tables are bundled together. This ensures that all of these related changes (store status, territory boundaries/assignments, sales targets) are completed successfully and become visible at the same time, maintaining accurate and consistent operational data for sales management and analysis. Without MTT, if the territory updates fail after the store is marked closed, the company might have inconsistent data showing a closed store still linked to its old territory, or incorrect sales targets. @@ -78,7 +80,7 @@ The following are a few examples of data lakes: * GeoJSON files stored in Azure Blob Storage * CSV files with WKT geometry data stored in GCP -Generally, data lakes lack built-in mechanisms to coordinate atomic changes across multiple files or objects. As a result of this and other architectural limitations, data lakes do not support reliable multi-table transactions. +Generally, data lakes lack built-in mechanisms to coordinate atomic changes across multiple files or objects. As a result of this and other architectural limitations, data lakes do not support reliable single-table transactions. Consequently, traditional data lakes present challenges for common data tasks: they struggle to efficiently execute developer-centric operations like `DELETE` and `MERGE`; modifying datasets often requires downtime to maintain consistency during file rewrites; and they typically lack the sophisticated performance optimizations (like advanced indexing) found in more performant database systems. From 80e883e3196f5c618a9afd23d0f41d07323ceee2 Mon Sep 17 00:00:00 2001 From: Kelly-Ann Dolor Date: Wed, 30 Apr 2025 10:47:43 -0700 Subject: [PATCH 03/31] [DOCS] fixes in formatting and concepts --- docs/blog/index.md | 5 ++++ .../posts/spatial-tables-data-lakehouses.md | 25 ++++++++++--------- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/docs/blog/index.md b/docs/blog/index.md index c76f6bac294..a65b8387dc8 100644 --- a/docs/blog/index.md +++ b/docs/blog/index.md @@ -29,4 +29,9 @@ title: Blog The official source for Apache Sedona news, technical insights, release updates, and best practices in large-scale spatial data management. ======= # Apache Sedona Blog +<<<<<<< HEAD >>>>>>> 97cd25dc74 ([DOCS] adding blog post, fixing blog post formatting, adding blog options) +======= + +The official source for Apache Sedona news, technical insights, release updates, and best practices in large-scale spatial data management. +>>>>>>> f2bbc6f3ce ([DOCS] fixes in formatting and concepts) diff --git a/docs/blog/posts/spatial-tables-data-lakehouses.md b/docs/blog/posts/spatial-tables-data-lakehouses.md index 140f4923abf..e120d547b20 100644 --- a/docs/blog/posts/spatial-tables-data-lakehouses.md +++ b/docs/blog/posts/spatial-tables-data-lakehouses.md @@ -46,7 +46,7 @@ You can query tables in the Lakehouse Architecture for business intelligence (BI The Lakehouse Architecture offers several advantages: * Data is stored in open formats, letting any engine can query it, avoiding vendor lock-in. -* Lakehouses support all the features familiar to data warehouses, like reliable transactions, Data Manipulation Language (DML) operations, and RBAC . +* Lakehouses support all the features familiar to data warehouses, like reliable transactions, Data Manipulation Language (DML) operations, and RBAC. * Lakehouses are performant enough for low-latency applications like BI dashboards. * Lakehouses are interoperable with proprietary tools like BigQuery, Redshift, or Esri. * You can store Lakehouses in cloud-based storage systems without any additional charges. @@ -66,7 +66,8 @@ The `stores` table is indirectly linked to `sales_performance` via the `sales_te **Impact:** With Single-table transactions, the updates to the `stores`, `sales_territories`, and `sales_performance` tables are bundled together. This ensures that all of these related changes (store status, territory boundaries/assignments, sales targets) are completed successfully and become visible at the same time, maintaining accurate and consistent operational data for sales management and analysis. -Without MTT, if the territory updates fail after the store is marked closed, the company might have inconsistent data showing a closed store still linked to its old territory, or incorrect sales targets. +Without the atomicity of reliable transactions, if the territory updates fail after the store is marked closed, the +company might have inconsistent data showing a closed store still linked to its old territory, or incorrect sales targets. Let's see how Lakehouses differ from data lakes. @@ -88,7 +89,7 @@ The Lakehouse metadata layer is relatively small, so the storage costs for a Lak ## Lakehouses vs. Data Warehouses -A Data Warehouse is an analytics system typically powered by a proprietary engine with similarly proprietary file formats. However, due to many modern customers wanting to avoid vendor-lock-in via a proprietary file format, data warehouses also began supporting Lakehouse Storage Systems in addition to proprietary file formats. +A Data Warehouse is an analytics system typically powered by a proprietary engine with similarly proprietary file formats. However, due to many modern customers wanting to avoid vendor-lock-in via a proprietary file format, data warehouses also began supporting Lakehouse Storage Systems in addition to proprietary file formats. Still, data warehouses generally exhibit the following limitations: @@ -102,7 +103,7 @@ In the next section, we'll discuss how to create tables with Iceberg. ## Creating tables with Iceberg -The following code sample demonstrates how to create and populate an Iceberg table within a Lakehouse. In this example, we'll create a customers table with id and first_name columns: +The following code sample demonstrates how to create and populate an Iceberg table within a Lakehouse. In this example, we'll create a `customers` table with `id` and `first_name columns: ```py CREATE TABLE local.db.customers (id string, first_name string) @@ -124,9 +125,9 @@ df.write.format("iceberg").mode("append").saveAsTable("local.db.customers") Finally, run a query on the `customers` table: +```py sedona.table("local.db.customers").show() -```py +---+----------+ | id|first_name| +---+----------+ @@ -136,7 +137,7 @@ sedona.table("local.db.customers").show() +---+----------+ ``` -Creating a table with tabular data is straightforward. Now let's see how to make a table with spatial data in Iceberg. +Creating a table with tabular data is straightforward. Now let's see how to make a table with spatial data in Iceberg. ## Creating spatial tables with Iceberg v3 @@ -194,14 +195,14 @@ joined.show() Now, we can see the customer information and the location of their purchases all in one table. -It's easy to join any tables with Sedona, regardless of the underlying file format, because Sedona has so many built-in file readers (e.g., you can easily join one table stored in Shapefiles and another stored in GeoParquet files). But it's even easier when Iceberg stores the tabular and spatial tables in the same catalog. - -## Optimizing spatial tables in Lakehouses +It's easy to join any tables with Sedona, regardless of the underlying file format, because Sedona has so +many built-in file readers (e.g., you can easily join one table stored in Shapefiles and another stored +in GeoParquet files). But it's even easier when Iceberg stores the tabular and spatial tables in the same catalog. -!!!tip "Speed up your lakehouse queries" +!!!tip "Best Practice: Co-location can optimize spatial tables in Lakehouses" To speed up your Lakehouse queries, you can co-locate similar data in the same files and eliminate excessively small files. -Let's look at the following spatial table stored in GeoParquet. This table is the Overture Maps Foundation buildings dataset. +Let's look at the following spatial table stored in GeoParquet. This table is the Overture Maps Foundation buildings dataset. ```py ( @@ -536,7 +537,7 @@ Lakehouse architecture offers many advantages for the data community, and with the native support native for `geometry` and `geography` (GEO) data types in Iceberg, the spatial community can now take advantage of these benefits. This marks a fundamental shift from simply storing spatial data *within* -eneric types (like binary WKB or string WKT). +generic types (like binary WKB or string WKT). This native support is expected to improve interoperability and performance across various query engines, including Sedona. From cb014fbcfe4e9c193f022bfe7a7ca87f7ab929f7 Mon Sep 17 00:00:00 2001 From: Kelly-Ann Dolor Date: Wed, 30 Apr 2025 11:07:01 -0700 Subject: [PATCH 04/31] [DOCS] fixes image --- .../posts/spatial-tables-data-lakehouses.md | 2 +- docs/image/lakehouse-architecture.png | Bin 0 -> 53485 bytes 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 docs/image/lakehouse-architecture.png diff --git a/docs/blog/posts/spatial-tables-data-lakehouses.md b/docs/blog/posts/spatial-tables-data-lakehouses.md index e120d547b20..379c9c0e681 100644 --- a/docs/blog/posts/spatial-tables-data-lakehouses.md +++ b/docs/blog/posts/spatial-tables-data-lakehouses.md @@ -41,7 +41,7 @@ Example Lakehouse catalogs include Databricks' Unity catalog and the open source You can query tables in the Lakehouse Architecture for business intelligence (BI) reporting, data science, machine learning, and other complex analyses. -![][image1] +![Lakehouse Architecture](../../image/lakehouse-architecture.png) The Lakehouse Architecture offers several advantages: diff --git a/docs/image/lakehouse-architecture.png b/docs/image/lakehouse-architecture.png new file mode 100644 index 0000000000000000000000000000000000000000..e8ba6956b6ab55ef6fd65de65edf0c045e4bc042 GIT binary patch literal 53485 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 literal 0 HcmV?d00001 From f17b1b16f3416b1fb40ee668d837cdadacb5d39b Mon Sep 17 00:00:00 2001 From: Kelly-Ann Dolor Date: Wed, 30 Apr 2025 11:43:50 -0700 Subject: [PATCH 05/31] [DOCS] fixes image --- docs/image/lakehouse-architecture.png | Bin 53485 -> 45271 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/docs/image/lakehouse-architecture.png b/docs/image/lakehouse-architecture.png index e8ba6956b6ab55ef6fd65de65edf0c045e4bc042..6ec5804d0e16178e0d5cceedf192b99d57b45409 100644 GIT binary patch literal 45271 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 z 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 JD`gh){{XLH>7oDt literal 53485 zcmd2?gQ*0z5(8$E(4)9b z)XlG%I3gh*axykiH+g36cq`o>Bl;oQX6z3=`JYOqR8{vc|(3_>VSvlUMMS zQOvhyA>$z+5G6=fQcT@5`{<9C4~53{%M1U_SnFK5!W51DB6!~bx!6u0WWE*^fH+&_ zgX>F4d7*;xg983>NCLOaC;$DX7W|bKZnf?Z^@DEeR4*$ovTIoo_?hPl`&FbZY zY2t>$)X)9gkef@ap&YOA*^8fKvHTqHtKAQom(NHo&_;J+h-A=unElt51PyUbs{Z!0p zrr5JsI%^9x)O+MRsRJHUvES@DQ|%RR=;QO(*;H~rg1k^<`4OLg&W5d?=R!Bj z)1_1LIs9%7Ze1m|IhPA+qXOAi)nrGzCs4NTHUlj0Rg61FFJQzvxw84)Yz*6$r7iQ_qkf z7AYIaeWOEl3i_wM_UO=1#hsorUlH>4>uGUP^5(Q-PYZAd8k(B-k;3{7==g6>FzdAL zpK#EHyH;N|SPWV{`=W_5yxkx0Tul=%_nRo>Z;sR~{XJd9bIwvSb51IiJkD|sL@7tE zw*`*3u~KxHEXL?RfG*jKpa^UC7&=9;u2g7L}`IzFYZOg2O>j z6{FRkNd=wE@NP&esEY2P5gIIi?f^vAQ^Cq^@X(6Knvm2D_1$b*Ipr37 z7G2k(;acxUY~W2(;H<}*?@a=$q9Lv4q_FQlS&P)xx!EZL#a%AcDc@h{q!y2}bPvhs z@%j5u9N(ucp#kC+qkr)l@RlEOZ}H0KVt>7%4;f!=%t-jX7iZWrmO@Iw9F`NenSgmA z>YOd)&C4&y?0rl!PNQbn;&r+EPA}cJSFPDy>mAXYDYC350)!*cj#nb=I`~xUbmeQDhDiTHB1DYDCG}WTN|H8#yOE>_Y269gFwW> z)B0Nx5WCCn$S_;`Bc(+a?RO<9soqwv%im>XPKXu{R~bVS5D_DBUL?%MEtv-f*hSy>vhj`MmSti~nAL4kkX2Z;m9eTw*=nk=9$mCow5eE8*YzH<|Lj?LSYNCv z1@8s9F3ta)QRR2C+;q7+n#Je1YH3#uCJ;tZCF6>AE?8_$wcpRg8-wuWWb!5I87ew@ zO)k_7EHw>PymZ)>Vl9e(CWNs*_V~V~)db8F%&*GITp)JMS?Z!J`7LkJ3(^J`_`<1^4zrzwW3cACb=48tIMLtX4TI+n9E@M8rG%eVZsd!|&)awOepTN!uLuWA}z0$+tH@-)N^BXXCGcX*KFFuTi@9@jh-G4@@H>^}tXW!EwLaDKPjs zhgW=j-n^bHb64slakNwxB6#sHQS{pfB)Lz;(Y>< zCgJvc_LR>rrxfHw1}YbS%BltiBW){Qt{UipKt0X#)#Juj)BSUHuvt93)^cgNHagA* z+Uk_fgZ1X~d1#M9z{hBPqu~!h60rjPK1$C)ND_J>Mj}8vPC@aT{SS)`q^h%PayS#Az;U$Inp!O7m zQRQ*5Kr+u>;;yn~Q*IDijY8Fdplebh*@*s$EWad2DZf~Kz60M??2rxS-& zH1{kW70GiG_>G^Ya0qnLCR^A8lphP-Bl0p!(bStJB|KHP15H8FEpVN=%Hj*q|wEOUa$} z<*uo(AHWnlsEqTk5UA`?&%18k>7NmGltz9N=`b%<8YLQ`j5Q7XC$uo|O|H~9VzT?6 zgW?CcwiF=@xcNiM5Sm_Q#CJdNkyxlzJ%hTi$&<`6>q2cm5L3r3q#`1H#*vojVU?!A zavGtdPJHvGnn)!J^#d^{F*Vy~kR3)7)u+r&Dt94M6ftX9iFj|P-dAij$NoOgobzw@ zldS$8t)80givr1up}{SSI-be8_50k|e>4$PqU4~C0GXLwxy z02sE=OGA{?TeYPheAp>-J#;>QhsYY0djIV-2j_mBfD>r99{zoJ&-#gkh;E! zMc8*~vh*rPiY-dpqkA=>ha;nCy8XbnM zcE9#s;&YVEcKyPPf0MijF_#;KW^PW4lzi5OFca7tn-BiyjZbP*&!k(9D|OHOb`nu_7OX?7(1$1Y5)r*tsIPOH5_ht zA`O~d5S{j$)Bq_E7OEb$Y7(u?8zWx2#@IZHZgwta6TK^LM#Vh8uD4igyep2ahs%(h z(om0Wo@Y(eVd)($;Zx2MyQgw!EI6h zK43jro}~8Upzto!U2ZlfpsfbAq8vg!W(0rdEQ$2Yt;BjJS zgB;p5s-U2ID5+;_OP#>`v$8T3tz>!-BB`gwsiUP;M%wR0rc}0T=G-4k_SwtmeZ|rj z#|NnWkV{6)g}Set+%?pG&}<4)4l(;tS>R;*x?bdI#NpxLrSCaNxxdvFd=*h%v|CnD zo2zBoDI)8iv`g-nRyva+bC=*N%EdDFQ1 z>QbD|s=T|RdV=*VEa~0_rlyGscrl#(24eZIr8q*&&RtfKa5ev5# z6r6|P#K!fTPHNMz@tw|_(v$l?)|_iA@~1>d6?T_}QTQ(xc?wnv_Mx`doK5YoQ8Mm_Kd`CNm4)Xj@!1Y=sNzcSkTt0029RSZ1=d( zL=>5^2ZG}B8DQm5-r~3Oo)+_*vSFz7q#BzG_7`0;UW_&V%ia?4P-V{SUWD@^HjSR_ z`=`#>^bA-}^@J^kMDAZ#Op*haR1T*KQ!5DkUXFA`F%2qqCW; zY#e_<@Eh37h3T(o;XJr?g*qhIMre%)sR6^vQOU*ukp-xNQj67PIwXw<o{SitKd~l>m=P5@zp+W@nQzlHpC_zzRWfGwM-KP^_d4M$blY+mLS=wMD>Q&f;lGyMwBYiZ!|4LUSi~0h<8Rvu(nZO0hW<|pjKR2FR`%gtd$}&;b&--k zO4Xxe4Ez?%LCi~>x+qlR!iUVD1eqBZ{h$xm$!bc%ciWzVb538Vc0#91a8ziB=RRW% za*YWl#SD&)s(nVnrqA=a*v3&B8XUb8#IR#+{o2eyhyR_a7}_$QSD(S({wR5!r1Yo7eko1g zJ)_-kGWS)#TuNTbkxYyjL2_B5y$zb?%+R33R%2=o}f#6IUcJ4FtAVo zoZ^`nzSZ09BqdD^4WaWM93hH8Li)-3lWzD&ifw}Yf7o76<~N~3vB<}!pk!fb|MZyolBUDTmXqeg z%C*N`KDjhjAysoD>TdQCFL-#^D7hjq*qBPv*jUBbIDw)jC<6gE4fQ=E6?M0~I4=5o zMpo98sf#5jj8t!^RIk{YppmGG)1WAJB24_iI2$tqOH!(Nv~hm*S5Xy}8q6On67<8X z%AZ-Zt10J?2EQcrzEY5_=@~-fdVjuM13dAn@68JN=c&(!_q!IzNK_K=p;AE+F^=q9 zMx*60TmAJQqd;cJR>5sYS77iE69RtPuGFR7d}8DPC^KUrK%g}%1Fg;^SbH@fyVZm! zQ9v)NmHhjy*hJ%Mut@$2eN$4z>vNg|Yg)T{{cbOhjgj%mJhs_^*d1M$rlPEHN$Rr!BpD%D&T(1C>Bpe5@r!g08TRLF zc=o9c*c{&k!>SirRj@U#2jx9()0bpB4_)jG3p_P@o^O6mRMg{9Bv&#G>Wd+{ z2^V?ne6fCj9-scXSp`P%ZiH(bu7Ay`p9~ERP2~AJPGIz%0$ozWin|_l!iR^Wx~=m@ z)q{<23ct%yz7~UXdXc-qhW4l9%7;kIq--2cYnDQ)!usft=&3G9%xstGc9;?BPI#xC zc%nG|7mP{FBncG~i1Z|8h@3G}09cH<2W|+%rH8t@>cMonpaTb$P-b%(bCc@CSaR6F z+($5o6nA(&lgAc25-vcJ+Ue;kt)p$cx+Q%%&+M-yf{<;Jm z@d&Ao@$k4AxE~q1bFtQ|&9IS;!{~ZY61XJ$X1|?2jYbR0?WTCBwYHt;x}RD0TGa16 z^jGS)#16P2MaW=N4PX|PZ2UA6l;nkj!wES>ykwB<_c|uR#KDOO&P$Kv=Y?%xT&wD1 z=i(yi?&~0&eFHh7CvK78cVesacpIOUmDS|+I{a>P`XB@Ur|XkTuZ>UweAX|XeMFY7 z{^=H5H83jN*zh%LRIvG|SPoTV2^rI5NL^JpdBT(0?%KLo#nY^(=BO)KZ)4tS^Y%ME zvO+2=S?51mP2`IOBjf;Kv((`D@``r=7X0qv<+fkMro!>h_3XNg>4=7w{xx*T5!Um5 zzoeyow#x6i?DgBr%PR-U)jf$xPHIY7WKk^_Rgi%zK}3hmP>jR+F^VV2GN0Q!I@!d+ znFn?Ky;Zm5v3qs$CU4vqbIvN*wN7;A2%dNQDpOoPmww|8T zLmT8HU%D|wP^SPE_wZ9SO8Hv=A)Aa>Rg&<9@(G5CTVD*zGiTe`X`e$3)L0IP&5sqU zN=La63gQWst?=-NE;ya8R1&b>9d%*rE#@60pa$!&s>EDVocB|d<)F>iyjz%vcNL;t z*zVz|j%OU+pXYGy`qik{din#nnA9WX}5hPJ9I6=`yu-$!D1>ne&76H+A z^;K61pl5!%S);J)+j3$MsRxyvhW(ZJ#;F&Yfkr}Qzl2^;NG$WiN+v8FafI@%j8|Xv z2_1!q#i#j1EGjq>1YDKRM8z4#5d((vAq$?mF5~4Fz8-{s=noDL1!y^mxUDAQ{H_WC ziqPlS*6{LX+5hEfFVFuOcwCC?9XU6lf?vOWRa8`z8NH>X!L0jWCoWa+%k(`x4Jp6` zH6g@eg<@55@LSLe1;=w6%QF1<)jJZg5Efy=%k{mQ8zd;!#2YP^8yXKEfX2lE!_42~ z4Z{Cmv;V7JfaPnd#K`^iAr~L;X?LR4wpLbW(!|0mdZsVHvU`Zk)yoF&@j80e&8+64 z6SUP(U-b3;v|rLoYdxLevo2g|{WTQRnTPN(mD2%I&oJwBzegswb;5q3LQ$4k*6uOCta`&jAQst*wH?wVz^$ zd90_3R(3HuRLn%>RvLIAm2bq2mo6kQNi0UqboQM|-B|MyTorUXu$31Z@o9xLGr#nH zrYeqR5|v!6o=rY$6dsgqSg9JLP_y!Xat||xrt6XRh$mmy2t4{cp7&>DF=(YV8`u5$d;eEt3PqC#FGQaq$9ikWnqIO` zvlkh;LPlMe3Q63>wE0_!Zk;TPZqpWq|1aNvr(P0axZB$y(HnhX+q*0Zcat+SgHZF5 zNUY$}RzY&~JqaiNVpSG{QFM>Hx21cjG^=Y?yXqy{p}K4-4LNM3_p~XE3{3OpAGc>_ zW*#5+L>%qy_1pdaoNkduN^YLtZVx6|j%L=EKyEvXMHv*WYlX0pRRi(rs|ZN`9YPCR zuCr{Y1JV~fYK`ec2E(GMOl7QlKIP^9uNNRncj40?0f3?rs0c97)}G%_mt*?_uB}bX zU}9q0I==;Q>{S;E8|XbFwGGcMgLf#t4S6`NjL4n58z6g0ENc zKQz%yIQy*Z!5k}Eb*ps!Z%G@U=iOE)k3Wp%3PWF20zlCO!UV+YxSD-8ZQrm0jdAn| zjzB;omXx&2^M$Mt`JaC`d>nQ3o9;G|EXJ$3wnbwTTD*E|EkzlEzWXmef&VV_$IJai zfXkPPq8X}AbSfJX`}fpD(Z5DfYvDnfKb}fF5vc;vDFkFX=2q{TbVb!wwKLn8$8R+F zWgS6)((eP{zc-%y#c7;3-nrpIXBWe450L;7=0^7Q1gWYb&?fB%jpWc+jqW?n3S2|Z z(dKIsDUI$z(o|eLvn9mKekS5nmSqEN?ECY2JOi4G8|eebc8~teUIA(>r2P8Phvn@7 zHU|oB`!Cxf<96QuQl_Q)+$XJK@z}9%)7JOp#^wE==l@mQ@RhIrG8-6<(`A;Zx%vso z+5YEvLZ2^5GNA)ntGPMHh1wlyk^9MxK=16_ zmF4ADr>#CR0r&0QU2*_R7gOj>E-UU`r!c$Yn{7`Zx8+0;Vp*F zYD`VjBP#5&R#`-z_LT&V_nVc&&2=(54TBX$9 zii!oKc*N4BfknJTH3iNSN^@W^^TI{UQ04r+uo}q)0q~xQ)=?{@jvmAdV>Eii;Lc+itV3?0S~?SkM#6vW1Asq3Cj3%7 zlM8Ow_ku21KJ{GfPkVbJlO&ZteK@k$`jonzeyG^c@OP?T;I?dUFNTmrY@XV`CNll^ zT6Vhk3qZW5LnPMY8?XF78Td$@PgfNi=%?2*BVg86uw`fQ?IdSwC;^xykB?4wbjX}V z6R|)0BPRL7BrbztE+@~o^M?d;TfKJYsO!IKT`El55s<+g`FPvW6>fj<4>vdW7w17*KhfGhs_N=jp=>?{ zw((TOGqvW!20rJ#ugmG@PrA!ncoh7j*}$J95t9ybTTt*>uxbK@)o@O);G$7W+` zzd+(5Hb=4r#iiM5lDge%-(I3Wn90^1C(dGjs@NX@hEJ|KIy!tAU@-VLGU1&x^sM>l zp&z2;e3JmuL;bs64yNwd>`=MKnABTAI7mW#Bp~wc_g8m#$^twG)gX9MNks{Zm2U;8)={)mBOB8lWRRUTSCngOhZSo15E;jGL5{6kwkadcNI?`?#A9A7vOe3?!QI9ToENwkqhF+@{o^3#Xeqar+j&WbY02qD9{0v(*1dy z|4|9OVz%dIB)#xi2dvOXjRzA??eN}}O}m*wJG;#e>vzI-^VQ_3fgM5f*a#qP zE4u0gOtPdRn)Hr%ag=JxR5)tvH&aRvXov&UbfPfgqL{K+AO3MJ&XJfN;)KiNi|Z}O zq=x5&q7d9iUR+!Zv$htI@Urv0V{1%Nc$h?bc=)1D4@B&qZs<=ef zwxY#~q3tRbvS_CnaKPQ=-Wp(s$aq_B@aG=rqsMTMa)J4KZSq~l#)eV=*!AUZ zN{;R6`KEo%eti~09r&$x=dFSZ!VCYrefKI$QoKKE?eJ4O+xl%GT6@X6<>KU?c&o9Qvj zBq_n5?%>JB^~yE7H^r~!DG zjTyp_G;-i}bYvhj5jO~y`;TdIs(9mTV|lsVT%|tH_v_VJjM`THNnO}{IKHBMefx#+~w#E+VQ9CXaVA;d8xk#m~SJdQ=EbVJDgq#9|kw!>I3Pmmfl0@Xi$FLgq>R}FrF@Zg|#?D%T5k!SV+aW)J4i2-K)BC`nmP{X( zk`NC#rvl?`j{2Qy;;M}Z1~eiiO}6@Ca%iaXa1=jK$%sJbP5D+mw;bxe?Pgex30{t{ z`R>c*Kt%xJW=`Kjza*!P`F5`T*=;QQqrUzm5NcjqF?{ZBVrO3Gy#$6p#nW?W+5vLY zSTw>^w|53W`2iHG=go>o9zb=K0#L4JSw^o}Dwqp9k!9bdcco6s z8xM}KM@RJPs*P}xCDAXc7@>f)*-^gEaH@DwPM_2rahxP} zd9)=Fv3dh+O-6>@BA7v&Wj_do_21oJ_<2wJwzB%!h(7k8Um`rzZMk6lUe^0%Yf(On ze`eTOs5I@WndngUcByc%=CRBnPK7-}+A4e(PTSz9h8wq=5_&KT-r0>X8zT8YO+H?q zz&o+rR-03xU-3nQIV=^adREt(SNGWIlZ?nZqBpulbD(9Ep~!6~#;Vs|PBB1v>w^C= z8Qw*;UoKeYjoMv1v_C=dm^1-H6n!*bgn zYg18@I-hY@i2Rxv=v9BH`U71AtP4+#X{P|*0DODTuTcolc^-uwheL+X7ilA-uY^YD zTZ-1rC?>!}%WpA~P9+g`ROXQr2lxc^rnG~Y1LFf|7ZT~V)_h;;f?TE1@tMn9!l;}? zO}{@APn;B|86dxzd(HA%`fhlfxNa*|OKST1WJN6*E_Hf@+1R{{jMI~zE}R)Qa(vwM z@cWZp(^<9#`t@zJ8AA;ff=k3k;Wi{BX@2035Ys)(57wh@EVA_$PxB4=U=!Oib%OwM1v#p$7b47hc3LJt)(Zyw> zBlP@X!+><6yLdVro$MZ1T<&atsZ6`<#>tLA?CXR{SNUk7s<4?cCeEPmzHT|nVQS=4 zeTJgZQ<@*ldlTK6?fX^Tnaz8r+?lPV)e5W}Gs`x4f z23Z*(hDSGudq7n=NgmW{zyGV)A5F}|=u*u+$l`4M`a#P%zm3Mt%}1{{zqkkwm@alO|U9+pg02%gQIG&(pb z6o?;h;`&b9d8r^QWt;YierBC^=q+#-VwiPJ}G3|CMU0@_#x6 zfYWQrF;COqJ$1CRJ4${5R1g9yHrCd&u2(1qOnCFdNz3r?QW}m z%kJ(t02wORmi#(UM~l$z&?+qmCB%l8l0;UVe7t`MWg__>fV%tAmGTCnCiQ)mgnK(} zP$2e8P=E|uQ|jM32X3b-X|>qvRl#3@f`H@Fp4d2%McwwgQ76?wbyGP# zxhWzw4c^w)^%@W1`-SPMc8J54z>L=kwS2KDdi;nDj*WGKPVc zdW^z2j1uKDW6V_L*_m}@ExvOH!gmIdI4t7X&y(B#isYV^B(^b$;h7ABiY&>=b>cfk zpaym#_$fO>)2#Wz(6nLtf)bL^Je+TV_WMS^14cgkLrr5NVFxgivx9lwul{oPN5#xo zGftxUSg0%?jA0MPUQOWaL~fX~G#b8IpRUrMjhPdgZOjlj6dG5t*bfg=Lpbg1n=Xcx z{3or6&7-S@wmTcJB8}wDsS5JCpYVMm_2`VKt0Mde<_y!s2MJ*{>0n%!OOh~@u0-|F|^XR?A*61tvwgnVp+SBBr2Yf4~ zfI3# z8i|C1Fk{E0bDoDW{MLop*;T{ZRLr#DD zmT6Rvn8aYM+1&Oxc3RkbF-tMG4Or9w2#<-0(YMsG2gU&qpf-L;eLrd3iWRCDCX#^` zeu`NP7iUj~;kM;9f}vB;t(`doG00kpatOi~FKDOE>nuGU8lKOxeEKXq5p5AgqE~k%xs>E`7aZ z|5Z&l`f-eVb+TWQhraj=*ex_qM9rj`FDMoTh9=wgZu;!wKD7IlocPT@obz>s?kv~L zfQhuNF-UcS*iVc-MlS39OozHtN%{x4NI1ei-H@a2@?3OctAx zkPm>1C@41{o^gCW(ZpR3;Rw<%)Gw^JPiETemHtxW_V2YxeZYt*Y5My5=2vEMn11?+h!-jOD&M|F*Yhr$ z|DBv+HA;9lFwoCBmN{;-4#2e-s(`QnT8W99m@E}|hdwOg4vkh(66{0vzO4@Kz@jrY`$}uq$1EKJSNMsIQ zGvtKbtRtStk~yAuE-4Ki;2vx>?pjb~YnfY?2F&w5S^!L}&0j_l-@}TAd)*E*&L)%I zDA9a>fMC|D(B%O(Spj&HcH$NR?&;f{)0lyAejCr^wKGnZZt$a>JE5iB_J>g0-$jIhhHQ}r>U`*E`ekfK!=`n9DuIcCn3 zT<%;cv;Yux+Rd&2@Ct_T?fNSKzM7N{p0xw~6M!fR49tSPLtvkS;hu`DxFoS`+h27# zZ4iwfh#Rq-MO-^Gp2j%xSJrR6NB7$1#S>3MBAj)Go2W$@nQzvT0Pzt$?*>BcSQJsF zWLA*zu~GEpyK;kFz@kr(G5a=Yj*!;^0I4<4Ej0g%4Zwde*1#4+A+mZp`A8H96F7GI zcsaf{4Kh_i#NK!mv)7dwx1z15mTT#cjy3c}AouBh_^S7BnE}cH6!nD2&;^xW)=4tD2%>Hcb2<3SX}T}@xrJzYM@Zi14tuERJWX+^-F-;@W@QB z1DFs+&A3_fu|KO;al$SzX4h$hlkQh zF-}J8S?reCGuU>nQkudSAfmX{jOv*W7j3JAfNsnGVQI~x67#+T7DEu=ISgO!=K#&w z5rbT?kvU!Mkh(p*#{Xk6Gkg)-~vlS<0ke7V6)eb2qoP087+p7#T!rD%ueP zP5;v{KZxH7*SKKP!jUj=?KmN*w!Xgp9IY=ez(fF~L6z^Zt!aPk@zpd?*&ln_6eF*l z1gos2E0FuKbJ3d|rkofyy<$r`Q|)8Obw4a$3Xb=5xqmRLD+-$p!Jh5FSS4w7lRG^7 zJ*%#}({}+WMqcxFv^RD_rsS27yBzv%5($|7m+edDDeJks zOI^yZn;J{$PPR!=$7@e+;vjJ)O0qY< ze|r^$tgX*|8>jbO+3kPW_BU(E40-PV;#a|S_h5uC2JbX!(tI1Go#g1gHT*v1pON>O zkqF{8A7mLX|2v4(?L__X(2%+GH*$e&H@i##8a6d?f$ejdr?|#ac1dF0GX)89Z(*wD z@6u7ZpV^yVo{CEos&a@(gyz$ErCU-jqMhA7hi*v~hWul}`ij9X?I&A9dG7$E#k(77KvCTOsXaPN1@V}qV3L!II^ zZ9=I_!GXc>vJA!G5OC|v$==kB%D0p7iO9cYTK;2qACM)}s3HGacZ zTTj!8)WngG9_WRctgDG+IKIu+cVKn#a$zg$zUe2IPkhD=cwYugRU z+KqRSil*i+ihE0p5>iKPdEK~hOKt7NQQbK72Ef}W`tSn9>Jul&44b>Th&psOHFi8* zgzpgLi9!3&)i^g9Wrv;*sMb`ZyPxE4jpab`a3t{{TXvTj@k;aP(B=3@(mWjsq765>C<;9!^<$!@3DY+QUMYJguv zBn{EP1jS3LM@^UPknyeRwF6(1_72nJ@2Gnwy0QyDNk+-ZN9)r}=kD1kF;=KgDJ)7b zg~{g|)PXz2_6AVg1J!3@DDgZAc6N63eXsulUFBcEXVH<(OezR}DpFm_+JiQ$^ zXW%`m1YF0o0k#n6fKGi+yiW@ZTG-F}}$R^DXL7GU+)a|{n+-vWBnN2E! zF1A}*Rhw6&F#S-x2w$&cG!;MtBWj!EoG{DGQ+HE}nEVW4CG7i1%n%CR0Kc_=qsB~H z4eWb?v!7S_3dQed#bmor`t~e3kI~}q;&KV#mf%rb zTx|thgbIMMZF?XA07(0;%{4VO1qB5Vh&wV5;hNIjuLIO?@hkq1V@Hi8WFh7*adV9y|PaO&{B8L?FL>_)`i+vO${MYy1AN zijA3>6mT*){KdfY`x2ovkwTKdzqx{C$Nh;eBT{@HGJ}RA@+V;AHP2`%b3o z=!NU~F@>GVk$et(zLy~_pD#YV#0G~{%S6S4Bj8{^H}$zd9X5~WUI}ghQ##XI1_LJR zT)+E$KxZN3vXGaO3dJ-5A{*%8i(hO33!zpg;Eg{~^1oM$9|ufOX?c0=!2IP?_imlP zS}N!{I3^)eEYJP&6goZ(Q&zv;}^C>IQpyq6p(tsD;5qg5O(Z!_@M5o03*a1;aOWluXwRcqq{Me^>{M|?uk`O?En}QXkkhXC3->)1Ih5k zR~KNAp=Set&9DDv*9Xv00pEhTjwUc2jZRyS0~jE{SeZ)n*v|9O*Y~rR`0}+6l#;Nq>#U0HKC9G!?N_a(Rp8(C5)}mw!)hUC|%*k zu|O~g@hk&E-3rZ0IbkJTf0ASj&=C7Bz|lw`fwJW*hUDW*;muc8)Pf z!8%l(+BSwydcps`L`qUuQ@c?|sAS8~RJL9W3Bh+kF%WUxI-@c&=;i>k%-+?s5`~E0 zBc9e5tZgpe`zs@xo4Gb<{rdWApA1-OUKP<-+cwZbD`j$9&z9?S>JS)sZjAdu>wN#D zRl?ELmE`&H10uF+>1$2}Xfmiru2UaThHdXyCeL9UP;3E(85p9JrYSi^#W5gcUjWk86s%Tg}kcCOMkVcV4>aXtp$|Dd(s$eZ;Efs5{bOasWznxS0>guh|2%LXc z@u-jlU4QS+%>ao(tU(m8YuJCu`y+r6SJsFP@Avy;s%&PFz_F{Z&l9GOP>z!~y*fRo zI&+7X9f?Ll9NP=qK%6qE+MJvM)m=Lbt_zhWWFAvq92KU^gizGEqXvx^fyApj)oyT6 z_u!;OvUF zuLmCB`vQ(vrV0=&^SpL5)PBk*%xOH13b&;;wY5F(iM-rdm*mWvj@JOL1)sCdx98Vh z2{JT(%s(4M6~FFF5&Zrynyxx3%IAA8z4X#uA|W6l-3=erw>IO>gXT3!=Or1j5o%z0l}&y!`L}B4Fg{D}yfDHE0&j z%-bK){|JN7y0~~99G{r}Y9rYGYwl_dZsnOBmZZD2nC8~>eKatOGgm6GOpb&z#N6us-hfmQxN_jvHk|IX6S&o5)N9{5tol>4A#J0P%+O5GgHJ2oCBXsMrE`KO&B(@Oup5rMtv zbPo)?6~HVk9#$c7&_EKzz-!Uttr7wp>k}JIa3EX&t_{*CEnvZ4RQ4@c#=*sHJuh*3 zyH2ybQz7o~pe(Nzki`e~b|MGiq{D#~VWDB?(fFcEB< z%!io?2s2b=6ETI=&X62tZh(rd16MkN6_x3`l6-s7H%aUZkJph=4 zjHkkVJr%#@pTewNSzL6U%WgV0V~=TNp__|s<2`E$m@(-ylvk0fZO8PmrTv`4%75~hkUIGu{XT4iN>3+hCPTrK% zR4=fLf`IYmX(K>>)N$4>1HdI(f?1D#xk-B{h0)yda-^dq{U(I*%+mnwWt!V*M~QvE zxzR>Jva@;Ynu^JUWf~l?doj3r_ubIrLC!+J&7pp+>zuRvkVu2U z=@$UZo(*zl;zxBky-(Qhu+dSwySq=~*za#ct_G=V?)n+tQ#W9ds()KU(I<*_&MI^s zp0)_HY5jeXAHz`1AT2Cl*|is8|7ok;7Suc2z{3Yk-ipyvE*P`H!Y|Ab+t` z^`D}_!3Zieew+?ijfH-S4d38~Wzy~|+np-A>M`fUI7;~mX;MG1Ck=R(l$V#cVogPKI48!NJxg^7P%(S z*G{B5HhcrQvwpUWXa>U10L^?^4Nle|SGea^_Eo`1#R>Qv&5hP7akH*S#R1@Y+esx=Q&cs8f$kvT9lnYMZIZ-B%357vIUWC3Vg6rsf%j`l~ ziB>SHF;x)nT#b{uuH#MMmr`DatN`?nS6m=C{NXJS#+K>D9@`5D3Z7pu1Py7|>s*}SobSf1eisGjy=nEY+yxG=n2A83B*BqdELtr74rP4_1kT~Le+j=xixa80CMyx&FcPWo#)cAGw&ve)!#8I4}!lB!<&OuaZYD1IrWu5mx z#rC(?*)E5apKse73yoU=1QG<%w~**AkWItdQBuv2y4y@LzCZG6-B+&)3$Tnwo~1qs zem!a{#OugcOZ^KxNN!q!8_ZI8`D%JRaI znIaH0=+!*}+sNh8MT^&hOYgb&DInu(>FHg802JQq9^@unPqKq4`(6n>PmTov%S?!` zqZY>*^Lzj1C+T2sC`_h-VRfwe4;>Z(I;q^_Em*d%hoa6SCIlKK=8ux6gIW1>8TO0! zC(5|$Vm5^|56xMreDG{a1btLMW-Jf{6V@&ijwV66Fay(L_x z>3Swrv|J1k-QKgHkFy&6+MFRspVGRKQ1pqjS;~p@PyXNEHWcZBUAfqC|L%X{@D(Dr24*ieRbQ@YWLMW@I1W`gyKKI#h_I}8d?xc*?fiEwJ92W|+GmQl%7 zziTBg|IQ{Qiu|z{SVBmgLeYZ`gV5TjbTC}do$E(gvyLt@_)O;>ZaEKteH-4}gcX|OGt#NGVXDCLH%}RW1q6|UfZO7WTNz~L?hPfZQXG6{Y1G;vv{M@uA zTk^NIc9lo^@X02dGAs4Z-j-kO_-xb_O^{e&6pmvXIxvcuJ;_CzBvm4fR$qDGHDI&+ zhfJONb^~I7Vy%lrL)AP?{n@jA25>@0^7_V=84fi|^o)!G20*Y3gy+C4 z^BRm_bY^NH8X6w^VQcPaqTiPDvVyn%scS z6vfkW3AP!e&tDEsYV@_?0NVsYmPp`_8{PIh?Ps%iehF-*^v3(yvuA;J(*z_;(b$90j|eJ~&0LP0F(Il@ zg0{MH(mXXnF_~>z4{i1xEEKo6^!>9dcf+&YUkQ&$;?liTd4(@WXq>*`d;w$)DINS` zdie_lE)`y-&8gRnIHu8VymaNSVrPr->aJyOmxR9bq*tVx)C!T}lmPH1Kpd2ml}S6V z$Y^P4WiS&4%VdJ6Fj%>BbIrl&p%Rn8}Mu}1;V23MINDLC{G66lvb>m#ut z(Cinhv)(}{I&H=Mg8MHx&8HvcRm{T66kezxZENM!m*?_nbDthPE|kx;h>Joe4k~G2L%&E{>0P zc7EU7v9Peb<^H*8d9d&0l?{@L<|kpTmq^P1 zliS$b#9wceu$K#Rz*T|vgfd%S%za~ATrZcAz&5O5OxGQ-ZRYVDt5&b~fVZxHibS)V!A zc$D%O!HvMkbW_71h~$1cl^Lc(IfWZHbN{paD1A0AMd0wMqta`AeTP9)1L<0 zKU^r5ShQ6eJ)a435+BoI)>ypOG*#egB)OeTO!)d*Ub2(Ai_-4nf^+V7ypHWN{MRyD zo(_IoTVE}dKH?>jKDhV-IqjCc2z+r&!!6oW+P{jmF;VTR9ri-;q%d;p++9f6qo&q_ zD$GWqL9F9&SncrfW9>3+l~;p5x3_Ye@wnDQ|l3BzD8hB92Q(K2RyIH4b(4a8|GwA*A${K5tKCdpVdQ&Zwz6_UUd+bx)>cFO0`qk6EOcE& z4i4cCB8H4hVs}=Q&>D`O_(T}odvnp4j(ywrU3*v1uL!j+8jHBhrr;8RJr7-YqY9T5 zgvqK=s=`Ar1*|_02Q6fTxjDM)5Wx`|bhl=` zsEyuV(wgZxx^=Z1a#=mCz0p$nMT6eX1OBL!ACa;VYU@bAIk40Q~6i+IsrcJL;lp& zBuUe+xSlil&(a6%ZjyyXayckU2wHIG=SkP&M5DXiOXZDG;*L;=9NF`4`9%tvbwNEn zXliO$otg#J-&~gO-+p*im06xyS2(wQLAF7Wn2;DI$J8p#8{Jk~Q$s#~J)UD@8{kMU zqCO?y8r0Y5bpZ(*8s`;<{f;t;hXb#vbZCmW2tMF*L9UtbLeAAqo)q1K76R{2a@~#& zG8G+)2*tsD*CI=lly@9rjqU(KoZS*6sJZx$WPDS0g7W08g6u0Y61SbL)v$IQ35vP~zSTdGSPTb*kh z@{3EMIbvs_#_<_m6xOWZ{hG-CQFFh2>^II)XOuyM4dctuhz&gkH?Z0@0qVbLf>0MnxIHb~ZVU3wOE94hrQLp(XgcBoN7g zqe;rYFk)6AI$4mou^GMSZs=(aXD8xV1=}N(sB7?Ncm2$|m=G)JF3#C^N zhg{>t><09t|7aNEY12&TS(rTiRQHe~!$LcLAJY>EPdKNo9t&igBOu%@e*(X_iQWdb zi+<=mZI_C}W{NC&6p4cnQ-v$q*xN6(+)ciV!6G=AAEZouVk&2&smZv)^Z0S9h|3F2 zO-&>A<>QOuA~BiUxdMREi=NQuBR*XlEi7v*-@KMEHmr2s`olad*(mOLj5(rHmSoET z7wd3wlY?sN?47_A@foZ|{+tLIw*~I*MzK+_RsC>aZEcoMfu*~k^Y*9}_~|O@z?5MF zK{!_oSdf(mGtKIP!n7k8M36M7?8eLT5RwP$g1xJJBWkJo1V1R@?Hk!$+R&9W+>ua5 zxgZnF?V3(3DM3j5U_$JvK5vitFwKL(`sxW$5}qcv(<|wy)3v9#!X05$rk13tokIAX zRjQr5IeZb6wp~-qtT8FRUAaVPc#}eE^8#j`Q@lA{5%3&-ayyR(v1XQnTf1(Hi<{V6 zoUtq&5%a0Jb>4@f<%5MP4jl;W(@e0B1zd?up|NDb<-Xkn!bxSZ*|p&~2_bUza*a1L z39iq5F8EdxoS(nX=a4eI*eNQ7#o)vcZ~cxFa{6Ux99Ukjr#bcU6x-*@r+py}n`8jw zWM62%&`!?CAo+dc9`lhY9OsGnM@@=UPTVKLFE)pEg^dC}9| zdPWmD+_q%P2ulVEJs?yKS@CMUSp*TjuSfg?2Ch`7^%wL+M~+Jgo@Khq5OkN?5DcIZ zTnAy)9@n1Eu{}zxAi!G8aag)tU7ooYq>fQf5n)Q8IK94A48_RHv%m-q^~q{7;guo7 zfS9v-JYBB8bO&jA!~D-i(=#)cD|-0vir9_C|9yWO{`$!8mawGJD~ym{TfSZ@gU~;~ zaQ1!v3%Q_=Jb$5|?IPIu~7@+eewu9EG_5#t~Xh*bjmZE5bBC0nK7g~ z`Ik3L+Ly^*IhmH0o3=fIk+D#|QP>*;5{^3`%pH|Vjuq-w?8hJ@3w0(qDt#jdl~dIZ zV#y|tOED2U5p>V?3ndkW4JuBBB}+d2us9c+tgME)>s4?y$L3R7l}}oKFkOB?SN#uLmkD?iq^2r0DO2jomP;4tA3O9{<;oLJlS* zkCF8G2A(Y=!hJbmbB*$DAu|*f0_~`0u7ivOzlc+Ayd0Wzth>C;VP&f_+DWd9iP??& zN}N?3D<|Sy1nq!AD2%z-hU;-UIc7q)Ah-ykGqt87xoRfg5U^4!%=n#c3Y zxW_kh1~W7m;LDJtUF zJ^GI3Rp+2zPS0H3TqOfCvI|k`R%Ec*Xs5EO zb^%EUrsGoAw7!jQEjI^Q}HN1 zdNN~dPfqQ{C=pp?o6=iMff-`a9XiR$n&EqpW@a_Q-Z%WrnbByeCy~ea>rhhC!y_xn zeCg{WNt1fGs8=%Nm!v!p>}Ch!v$C+7Mlyo2t{P_T;>LhZT&Q7T ztb;(tn^!X-+~_3cD|!`EV~#|(vTe6pwF&n0t<3b%GQ_-adY=30Z|vfQ(4g?| zF976HJ!^CWv|Q9vg-T4yhr4^(e&Wo7Gp_T_N3=qRx|$LZH0xjr^&Fle*a!oS0}($#u%Sk>?lag4e7Bb4oFRApdsP`B@tVU9$hASUWn#w_YD4B8_hotx(#wzQ0 zsGaZlu62L@B2BRGD8=U~?aPf$vDheA7$R~TrH}%G(V;6HmLYuHw3hrMC?Y7Ra?UWE z&m>&v5Vkf9FWPo0FUnQbpp~{w!K8l%q`V}7@2W*O}MIr-aQ8{Y%tx7@$DJ~lC7bsOh*RA ztAx*7DnPU$hD4DMNR@)1&%yMRg|1mvSde{YM8M^=KU=3{+i)-`KzMu1@~NV_#eRV# z^IV0+ne;a^G7551GP10T;o--&LnAO-{_aezS%^TDMPM(y< zF2xA)|Bm97qc=R;d99vu@9`ybcq4Sp*4EbCL|6%|o#?#!Btowe9Ykf!5O6FR)s!=4A?0{#u758SXJQ746p+NhDk-yS1|KX<0@@N8>CvoedswmB$L4q>Pv{FW&% zb#-U6;{s(6#R6Y-c5OokL0WhMPnjAQf^kZ?!az$qL$@G7ox4)~ zYkC>`NvkR($e}#)&11He^J{If6`}TtFMcyeI5wF z5l|mnnPHgSwzcQ~E#)p#W)otLedZsK#crI?iUwok$de&c%qq$!VyrQv;bW$b3hf+^ zjZG{a;WpH9bqxs)THZh3Z^*Z4$cD>8nKq*I%1|L29l57Wrj##@*bAz1lP7|=xckct z)YQ~GJgS?908WfQ(rJK#{J`yhSO93>S}Tws>vtQOJEN87;NUQcuHrb6yiZA?ZkmmS z-_}vKe39&0c4P5R3S+ys!__+xVtrixuJe!w(nq6T8St2TbfJe+s%UV-uPJ)_WtkB^ z1PCW}9(O?NLqD74ee0g%e!n10BL4L5{V4x4?`f8&i5GvoFP>a&eXKLb^qkAi+Hfsn zueHU0av!y=lnn%#soZ3ra8_XOVYUvXx5 zN|p*gBVdg!;Ze8kb5e_oF9nzfz-hMQ{sEplW|u%+#vy=L$&3L(W*l!=TM=fPjcjI( zmneF-+;}?vR=&}ecD~_WU@+brG@q=d_+zc*RN$rMY+(kTm}dJsdoRyiYB02lV}G0s zzlr({w8VlbGzRLT{+&l`Lrhc`UWPsF52kxKrcc*+Qx>E7?Dok;Etm6)m-HWuo9;(E z?W>PZs%0yCeC*Q$eYHD94A9Y&j`(Y=qhSIQ|@sycaqs;3! z#XpTY64ZycGkEfbhUxPRl%pRE0vAjOdQOSE&{P6Uupj{1wDb0v#epw7`=^-CT!v0> z18~iMvEF~s4tRvQRLcB#ANB9khkv|#d)&1tG?cvio5%lB^@Mh=E-dgkG>QLWu#1c0 zc+UgNX)xdAH`16reD&9jRpYBaP&^U*y3S$qYIPxig#jZco| z4o9-?`>Hc4S*bEsm4wS_beL%;4i^bn`JCJL$<9||<7CQcQU!7~OT6F3j^STxVc+L4 zAGn-LjV|qq_4W8BC4BH2W?JOIa!}OztGALJr>B}oH)v~f(SC5$ls8LBZ~zH!%f?9PZGCM{;{kxoT+LEnU5>ry&7=xE}IN-dnD zo#rG$8Jq3=eTY*UyH-E63@j<(E6SZRHzG_3c;Ld0EOk`_Lmu8yS>%(*#)qcI4@{a~ zDx+a~_5;Cc94})*L?um4-xsBxnG=-Sp@`h$XxKHe&-($r(Nm%(h5s&m7!~vTiK1Sa zasz<{f$)m@-L(SryuakqeNbpE+_)kt*`0EZWf<%j8#+-e%Iy2+vGnEJ>7r}GwQY^8 z&v|M|=KQMVL}(99CqEc~E5p<1+0TYv*&`|eAG%R9U80ThfqWn9(;0qKX%Y+8pLg-P zkA&#@LxT;Un9@Y{pk0W0sbnouqWUe_Y$O&oQi*dB$l7dUIsh*&dXzS3|6I0SltvI>)}Lq%#!mL>jcpS83>0TrNyH7SLwdV{s~@9CrPB!{7I; zhHKp!07W`sRye2@X;KyQLc=KWNbsj0?aPsv&yBMyIw=asTl^c;jT(WG*U_U7JU=S5 zyM3K=bgCWntkHdkKe-3vPLtJHtMBdB$8}}13XNnga7#>+fW;$r$ajN!Vkal|n?HqO z@#F9O%PT913wra7j-|}~`+1h0Xg1W*Je!@F`J1#7m$CD1bGJVv*pRqx*gDRRf<`N$ zd#%j#f|7sco3OL^J0I}`@=A=t@86@HaD3Us7sg+u$$j|$)=75`bD%9P!DrsLeqD@c zkJ$q_G-Dl|&_~(Ydp|ZRlta*Qli>|qfHyXlFJqK(DinFB^uoj>N#JM=_2@NP5LzX> z%K20E-V29?=hO$&xj}x+2{9TOXhzLmM#|Pz{SOTu#02^6uCaQ9_ANd=kDYfG4M-@t zP7`J779Mb%An+5+;RtwQA9tO5NeHERknrZ{^Uv+3)V&%@ znBRrv^a@7zEWPHjFPxHijAPpsaQ*DfA)J+{3q1*LPm zAM}`>IZut2PtKLRo3jrljj<_M50yqUoowO=F$NFdwq48X8?%8=y(GPG6el$74cglr z6&6%xO7K487dHMwY*>o?CJ65-y(eEgIwQjVT{n9DeI@%tgVHo-oW%HAv~3cT zeaShl0_zDen8ct=K$&D++&UdWjHsjd{Pw0~+XJ2CDwR^%g7QB}hY<{Wa@N``m0iy= zeiN(V;2kJ+(Vg#<;SvY(cUl~d%?bWq>1*rebA*N@-SwR(l}7yw!i@M)L5&_m@8d$y zFx>?yr-_sSem(h{eb}Q#Tu=WM>=kS6B`Dm z=d-cxhq6>>=drg)PhieT$!s{Ye>Zq_BP*A(>WSosT8d6P0qSd&+3sa@k}pRb#|0vR zZyth|v@F@BBhjECLN>!m3M_Y|VYh0EUj1Ye(sa+HqNyUcwH9kYJws2ZlbTW)G1yysiMzF=|a zOpZZK^3nD4n+Zn*W}&LaEn0^=lp6m?NQL>Ij>ucT{~n7%zQ$m3k05OwowNSREca|> zYgK8^*=)jbE=*ubu&uA|mebx7PTU{G-!3KpKsI$A9{szk=P};F$IMYn_n<2h;-!tT zxhKw12eA_b&XQ)v{&VKSu48^yA>F&)g9^_Qo;EmAekye~t1#_sbG*cKq+KumaNRV$ z+DA&wX^t!I><|z_l&X)2PavU@Hn;l(E*~Sz?#&m0#*FPv7hfFw{M2R4?iBk@9aHWO z%%9wAJEhHaG~<-yDtR-x5SFX=&TN)zf*Ez_;SI#9-K(y|?DfQ>JuCs!C9kVF9+CO2 zlVEywCak<>don$49Pl83T(MFEmbKx`b8cQj7l|PtFSrArjbBo&MfKib3meP){ai&7 z?;y-(EODwCmmqCBK1Pt07vA$R={j@w!9|G1MzN-r*1K-c_r*ZH8aI{g!3PvBt{?L3 zQJ~{rptwEnIl@lwCVU-XDBp$<5mET^6<^u_(WbmE_&7A#FPV!gMFA3z_!{FN20qo?=2_*X3U z`%xEw$a&rDD@b0zobY+dJwrw)isywvjR1|Lvz4H z)*%wKPZirUHeB07V8Ts~y#%OZ@$ILPpL8x$R%8t!s+AtSldoX@XrjnJA}ha+Wwf^& zG93eD0?qI?A7%bvZxC|E1{*+4=6ufllXf+kVnASS^1rU)X+O^<_GguY#-K0B63GPx zFIs;R#nbafY=T|8|0^DAfz}ZIZaLw5SVuoeCldwkQ|A?IFG~d|69Q6<@dB0Kvr{0~ z^+B3+a1ZtnWge~&50nWTtx=PrRfdj0GwUGiYS+D80Xp!ivNe2IZUAf+50d+3pfO*& zYSU$)7Q&z_=|(b;KU}&%3|ugo5W$8V+Pb=C8q_gZC-XZ%Hu*Ui(iiC48jJ`SwXSc{ zXR{Zg7`){w7|3@L9<=4+65ui-Ai(zl=fkY;!QlSBjUTFJCQr~}u#&&1Q!RL+GLaIZ zHIlk)YnT{{4meU8F(Q0B{fv>V5fceS1@Dl{!2uVvUy)cFv=gZAt!`i^eEB_~%7=FV zlj}#YU_qG@lql!&p#L=$Q#h=LtOFj_Ib~>ze8iDpOg~=2dHF>MtizM>F=!iF(~Ks0 zXA_YeG!Hlx-G`kIiRsYl3js@);Wov_NA^Wh2Q9P-Uj3vBRxSlb$zE^?M}zaMDe zUrDVujQ!V*2_2wCw{20hOo$iY3ld!Yrjv(cpiBbvbtFZ+Wogu)P1^p!{E3Ooh>sX- zpo<0OM&UR8+^t#CK9ZBS=b?Fh=3D< zYp7}l**WWRoma?W$Ld1w8mijbxF!Ea5;9P*lh zHz(bg$)D(1PGdaS7?}|;Fy7d|Yr-pOwsMK6Ys=R^_<}~^Bc}zEfVzpP5^_$dgE?iv z=i_5$C*{ja775$KM}F<}m8Rz>N%DhQ5D@zBDln3IKuW%R&P2eZE4l}#>7^DJ5qH`lG2&`CTe{j4=^OR&*#QSpgoV zr%f2>W$&OQVK7@Uk-c!n27mc6JrTqN%rxl0t1!W<;2b8cEcNB~L@{KSiyFW#Cd%^j zmFSVZYT>NQ2dwlzTlp4b;n~l!tbv!J8P=&Ha1Lxi8rW&&D+h-Xefy*%H(6-b0T>RY zwkr}aj+3nAvtJG7;EagtcgP=Y-mypZv!jztKx3@t8da$94xWVgb+3b$sFgorU~x={ zek%v&fo+2Z1ZCyd3HyA#Rx2>F7>=Pi3!E?WZL1s!Z#Qn=g9SV9Mgb;g^X@7t0rcFu ziJrg!HHf$f3JMM;@n~GUXGBco0J|tEvK{>@xhwU-1wH%?e>YO${QIk5LC^JGk@qX0 z`aJ_@0Gw|+UW7gp`7dASO9WE>+gUI zWt+y3q*IeCia1OK&i5;I+z<^r5x+) zw*9DTH> zocM3yIWH(Jb^KXr0Rd7?K&9g`mV`3#JPUvMr(i1}5epHc2uP~;ICdtJ6xG4yWQQ-fR9FG*Fi5aVF@*nD+TSG;$b z#NA@h6Ulp+`AxeWN|?3k`oql8Uf0l2GFt3cE{5kxo(JW0mTH&}6mi{E&+9NVXM20h zHhk>paBJ;k)kRG0Zq#Z$nJ_Unl4wSux+KoO9ak2R{G+(q`c3)C`E^kYKAWvr|HUJ= z5iTQF`+k?n$WzpY(nsj6Vo?0_?^d6FEUvFNG2MLxuR67d3>+f$xXSXqGcS}Fs5-v#HH6?ulI>TZ*bVpb6N{)EqXlRwMP#@vpNeD~K` z8I499O!d_TEESm39q58r0tPEl{7s;}TiWBQY}WkBx@6)L6Afo?jg2a#*)%S;i|ChY zeAn00ONvqdgzPbM8;HE!>yl9Grt2qq4kDD(z=;FSz9--%mAd-zpU)cuzW3B?eXnC*+N%TeHyBs&ay`2RKD^|v}X zMWHf<{M59hvaB1%^O`fHGo(vzD66k0g9v11vQHIogm6Zp7G(H6FB`-#fJoE{j*N7H~Mt+~dmL8v1l!&LEq5aAJ6ekh5`eBk4p^b3{ zvmP=_`*-9-qkDgarnN(gaHeG=uIM~`X%$ZLo|oJMulTY3w8LqJ2PVpA7-o^ut=#y# za;Me?pVYNv!!aAtaqb^Eg5-F+!4lm`@`C9cdp*wkhgbya>G9%LzCI1c>~d!PfKWSI zX50$2@&FBm(9Cab1KktEH?S6tC@elYPx&6DkKW&GIy}}ag*|;vpIs3pak_Bh)b#JC z7k7G~=d`=3d}hT_Th?zao>C`=LsE1|L_~L5Y`GaFB+ZUvT)rqnfp)vT_KHenDD({D zQX+vit?A;H%_p3Q?*8XjC zmex=c!Qy4DDn3UoH8o?ZjGqY|qxQ^yutS5oQ9639omFJoau!c=ecmp<&}8 zU&X7dORZOD6vt+xp?Dg+!W*lZPh@H5&1+aFDPlVnlh!HIiV!=Ef9ZF2p752+TR#QM z`^KAY-ap^zi)+-uc5H z$tYQ*oPF5nl6wL3FY7bEM{R2iJxU=S!uD~`kVF4LXQ|-O>ui74ng2JidAoDHfL6rw zwfmwU#ykz~ZToFUzo^0)_B3b2n=dcFbK3VdAk6r-AYSZV=+j`4sYi+4gHczm4yOfe z%*-(*FcHw&+S%aGS5Ow3C!E60{iwwsuwr^ztER#~3cr0ZUf`EvFZbxgM_}>TePTxF zP0eoC(D?_04EmwzdNTnke6{bZIkjHX_w}EH_s)E*=j^^2$$Dh@BDUCG#}o5dB#_V* z4%BBt=y^&~gcrBZa&JN(2>k8KH3Zolc)0v0XZ?>vo4k8BFlrVOl@P@Mk7LZ^opI~7uD=?~ttdjDJ`wWfg@HP5l*6E17hX>D zCt^u90qiMa9kdSn9z0BfSD5=xlIbukaZw@Ed0!*n%Q|UZDL>}d5VL)~d7^CEjw;s) z(A;|Z`uc!IQsZ$D_J@#J!1wv7TO2$0(E72UHlxeoHK3a$yd{zo_v zetdd|+8F2$Gm|i#efYD?l@yZLhTVoUmD9W3fcMZC9+7xNC6Q)}XMgrNgJ5+6YT`7s@wMr3}OwZJItJM(l}P?nOrO5)3~D|mW!Ol#(tywmz+>| z2Cu)R)zf3HKbz>1_aC!SkLF_VZ(f(k16lAAhr@B}AzphX8Q|;?7oliEnFzFKGo_py ziw%V|AKk6$;KqvIKMh$XLA=046HiDVc`dO2^*H@@XHT$d-=waZtZCCXfbRq|EGc}& zbBM3g(#lTObf>ACMfz$)G&x*GuX7FWfcuw6o0Sq>&l{P#UagH(82mh_=tg@LS_)GS z!W{_`57AqAkHaJbA%V1^1i3&M>5UmR%&6mF;t$vf`@fUR692|~=Uv^-K&}y#=m`=(TpcNWI^|iHT`1blxV=zCrK#O6YIg8(;EVKK z(pdygc_ScV*GlC^X79g3M8c^}Ou{Q)3Q*4^>lkGlxU!ONzb2HNz1IupS@>2=-#T{d zBS#b&4kv_>MGT-?-GQs=D&7oOOiB$gY&XCG$`ZGH z%)`M|<dR#Y!zBZCi`k(6C!ptmM1E8`7Vug*ZB-EVBi*?%jWB z_r{uAB8G+E{uMpBisay+l~b2K+0k8^UxW7HhECiKz^LcLgvnD7Z6k)XR9Vs4u#vhB zsIE5kL|Q8MgU5zj{~UkSTWo(#Z*)yjQE`>8A1@pqr{aI3$y7I*b0sOqoWQr1mn{-L zBq=AJ9YOMAeeO+3Ze82zR0Ln&<1tB}-2fLT3x3|8h+Y2MfI!WFSPn+Zhx z{F$8Uxs%7|uWR~M--+V25upNKl07n?NG(dqtR3F+Op=e^GNkBL#Ul%hte}FzT>FWH zN4o(Q&=`k089q7VYz$V)6}GV^FRQ7_xo=T!()WE(+qG=1>BJgqyGt#a2nP_%si1{G z)Y$5c%1VhoN8HBu5r*)V^pi+vPW~HDEUoPC$?gBj9rdCl$k%xbeSkh%l;ujcOu3)X z_fcN-*~gFBVV?Wvf%b)d1)d|{b9m}+dNo~toNBJ#3K2G})Sp)3*(`Y++Z^MOV8oP= zx+rHmIdOm%bpaHrw59{}v3p$rT443u7w}cZ+T0MZlo_Y`X+YM!23j=6KpGcNQCt}g zc6O|toOn1n&%ApU^D!!34Q-$0of_NQ%;ki8X^n_%BOokQ>7-Kk8}c8LwQ5ag_sgb+ z#T@^>$JGdX)d&fNp+F*g|Mkbl=}Mn{9CORKo}_UoiIEOmX^j+Ms1T#uDs01fro6q) z5S~AxvWY7TMSgqKZZPrcmzuHwG@Vy2?=>`KF-Bv+%wFWdho=B& zCND29Ldy;QI5~kfmr-FEMmtwR9;N`@}&7!(Gev`4TEv7U8t!~b=+n}gKoKg-D!lS0U>lOs+6AfO+ONvqCn;RQ=zpFsq z3LqX}4&;qN5gtGt1X2J1mS7QATNnbL7wd6 z6`cp;l%~<>O*LiCvx&oHt<|5|&6nJ#-BV-+Lq{xSLm`Yq!67eH2EV;MkC6;ljSI)9 zI^^qUGv>%H;p==!`dag2jz|S*nuOd>aMpA5mW~JpYo@klVc>ItY;Or|EyW?m$GO)| zsc$}5AMhLtN#FGZ7u*|9f2X=}XU5A60Yy=ZX2t|urfC+)Sz2G;{gU?dJ~n8HIQ=)w zX+P9Io;p8timkk*VHCeKJ3T%Q3Os4}U#^A^4-W$gCcQf6RnVopZD;tM-r!dT@0H-C zK#v(ctGE05%VN8^pVk-_epto%H33DRd(=k5M$i620tk@MqF|^-W&!Rq5yaMKq{ zCw|oW?;gp6K`N1)owgLh@xnWg;vj8+ap!vwSqJJON#k9z82=9oaJx$Qq}&gPqJn{V z3)rlUCp$>3!Fh+;>Z&S88(?F(0F@YmTdCVar@(n2pVRX8MnH{|i;Ih!8(CQ1KYy$m zl-w3x_CEu(dFizX<_%_Kffk_n+v+ec1W6XOn*s7G^TzhIV6>0@x5Ym^Rk-IPevg#Y zb^`zWk5+pjCmLOWWZw(7Ud7x*+%O}FX+T)>9K}fcg|{mX?x{Q(3sbL){T96ER%%pt zMDk+gC|%U2M=@zc?6huD#F`O32!g_hC|BtIZl)o5Sm!m{EbLcvvA{VB-O~B}^j$~0 z>Gvu|cv=Q$)xx-x8v^b`IhSQm{#%|=0U?VZ@o-fiN_QSvKEB?>W=sb`jS zx=bB`EI24xkSMvMG2FkKKn!mH6v4W^2Q_Mi9TzU0{{3}2;d|MH6bhKu zH3r;feW$urOyLzcY4=+>|M=NXr@$Ni3vW;({^$gVH~~tP>jmEW;bP+jQXUV8880X| z8I?&yC_Df%+JKL`4^)}aWR#VA!PD>|$XfAWqF^%tCUzs}^2GwA1{EN)0itfywZwcY zsC|OuqgX)aI+41~K*o%oaAs=CeyQmyR{Y2t2%rOOPu5k`;#7nmJJT4F(z|~;;24C$&bvZ!e(%VJcTQz5IHd~)%9#eQ?1b^orXW6Vv0w;7)ZE$ zlr9S@%-oH()gE`9tbS6lE`pSg1+c>Qd$=c_vMf=Jfm=aRq>*(MhAy5Ly;{qD{W`%0*`OMthPX0b1aBk)AdxDDr{G!3i zd(mSSIpWB&U!cGo2LtkF#ASBAl$0L@L*Cu*aOs6An@QvVfsi8=w#&$-vEqXd<}t z%PmM_k{l^&-MxyFzQ06DWLs%tLBKq!rsavX(o2-Ewze*J00ap($-6(3V5yM|*aHUT z;jgW0WYhvg3ofHGX&qy|fLLgqrlF6|6>_3c!5cIvm#XTR#wF>MPcojk4JrC4rYKkeXEnWY8W6G@sL@LU*p@h2&k12o9 z6+B!K_1PjVn=QWNH47j6jepY^Mv@QbWX%DMgc}rQXWJtxw^&8bFAX6Yj zRrMvm9!N3)$*ZSfZ;^Hh%nQhPgaSgS4CC({hP$0aeMgj_rw=E_#&}KTMyHe4d-0uG z?>xOix);5c4>4tsA~v8Z!{!OHcpos_e&aq$w~S+@cnG7i=-}WW(DT;Uo^lI&9uUyt zw*ZU?Jhad&s1dA~U=cs>a{`iFtqq{Li_+YN4eo#=2lSZ$FAgb`4i;=`8XBZBI=%cf z_?%P!*RNkA6^UbLgyndgTCTRh;=Fo~)VhuyT}4inrqwW-4S@)t=rtYlZruS`aNzxR z;KoUl&shLcr9pEGsEQ*ssE{&Dkj&=h<_kjBq#}0P)!foisg>C7A$BvSA|KUGxV^q6 znW>_vrij^sfOcRIM-kQTgUDw>el8n~>$^H0|yz&^kAm^*gaPg!Gdy+5b#Hk!oap$rSSy2e7Sq6yB z=Kx;o_^WCcxkCW|ZSTG$O@7Qw2xv1dH3005aQvya1X4X7eDU8;8Mg{jm0*JdfjRaUsV#l9 zF@yXMaBqQuuLlCTx1N7}$T4l`MJgai?@^ion!>R;FnfNhk%K$Ohlc7`AJmV~J9+6Q zNo!a{upmt6Zj_qZ8g1K~LUy81hj8m$!zw|a!({T`HuQvM1ZF~zAjp=;0RD0|hMU$u4lyw?$L8~w7jZy%4vGEHe_F7A4fGL! z8Vtq-cIDFY`UxpH`M^mpk~RzkI$s_ye`g;6)>H?a2hsoQ>Mg*cTElFyRJ1nCCpmR7nEX$0vK5CJIx=|&N0rSrbC&;Q(W?p@D*_GZtlS!?E7 zU%c=8{er@=KjlH~oCh0S=jy+7k3U5`-Jgs*upc_8SJ~^M_l&tp(`Vj0LtE)3Tf3rE? zI`5sbEC>N*ya*>dyWU5OXI_6mNbCM13$)TFgc*8=F#x9gYXm>xuMz0|7HgKniA^9= zY9>x<=vQYw&Usi1MxO4Vf@T{tegrxb`)<-uT1?)qsD@_`yjM~(GAKRz05JD&J5c`7 zglg{=x}KkX{h3WGRkr0NSBlehnGfBHWJ4vJG45~=4^7O#bn&-`zn5Bl+$1>Bw{uTL zm6hw-nndPo4|^)!m!M53mX2u z){3@u9D6KCmqeR@L6`OZTNQ&oVdh8~mW-xcqvq-dO&p1~SAO`k4YX;(1-8PTsnrxu zxpF)xgH2{vt|LGq1Ksga4eZ!o8GiMDT#E1?`I&k zK!qppoMBqB5IB7W`uz2k129L5IxI=KHv#P%(haVQ@vVu z`|EqX)4vIo9Z?#HgtH_6AQpfco&Yb;PJ?dT_OC@6&y7l&tG%LM3;-xDEl@!$3B&Wt zr7bNIZS-eeX4ezP#8VOBI{?e;%rb;A^@pS5b4$-NWnfm9jd@Drwt(U4)AOD_@aUgh z^V8eUU;2*SCm375sy?i~8Ncais8{RYYAVNP;S@ZbE)(deK*I6Hms_B^zKM1(F@BXj zO`wB0X>%gl{B5j^(f5+L@HJM!=YdL@as2KkZo(;ad{a#iM_jg4Q+0!uk?n7hukqXc zzwSRH7k4zZSr$$eXu8;H8BDR!MchIl0-4d7BRNguVhEG(7Za0Vk&?Cbp{3DO+NRb@86fA zub>?fK-@A=!~m8MFn(LU$bg7Iiw4AZmqE}mPE}jK%5EEkiZ-ZsNk~ZQE>936VxIEe zxR_T!SG|13ex(23*!XzkpgUM0{cHy{jUfiYItbU%DpnVVm#>}B0Fo+WvC zQZ%sDH!aE}U_a_S>s@6`e%>7J261Sa$kFkj@0@2)E7XfYM8Mk*>+DHK;Ja>esaghO z3+)Ek`PqtnL!Xl&@5|O^;h2~lkvoWsfp0XE43lsA@gCBqExxgTPDnD$uO9fL*a(ZT zS}osbC*;%UYgf5qaw*nZZ2aL#mLwA5N>W&iR4b&o&$omp8B=HbGkE`7JJ7^le2@aMfGJx^`;G9xg3$Ek`;X>%szgQlQbg>Jk)h^zM8hRo z?6UH)leXB6{gj#PF>%VF#)OjWlA-2SDK3JR#ZhHto7K)wA|~vTc5o3FCbz*p=?{n_ z7Aj^&Gvq}_N24{9W+ns!hd}#d=c*kgtK@HZdzLfAg6AECdcL;>?Ge825V`Np^Rcnv zE7Bf;_FP;_$_qY9;4X!=7Hbq=!f89X)v(!eCeBFI4;nxK^aflr#1Gg3biBNdAeDHg zu69(X^nIV1SkIPZy2+iF%!)1$Jgft2WS)7vmtmw7u)EEPa|wF5f>gbRBj`~Exg14J)L!=lR8@meyqgPhhpqC7? z1E7{1fX@YTKfkN}zC!kPDf;&-11?>Bj}xf;?6lUn|FgK@Q}2{(N=!;hN=T4@$HT;d zK1#>uqa$uKUT}JywL8Mq7t7N^kSZ)B@7}$u%Yz4{-L&#|DEslQYYdHOgU9|Kw5$}& ze_2_65&uZ8bwP^#%F%K6&LVi&w);cz=Bn!oxG}*xB9t!g;=+Gp1M>XftBtUd*fUT9 z?iMsu;^X5TN1h3oL#30MS%;l8ci58WUF4J8E|YqY!5w?y?BOAFk%)io8kpd)#`jcx z{8-Q9p@hVY6>;$}?{}|k$$w1W?ELpV9>Yamf~++C-;HCosoc9SKCD}Yh(2O4VG^v~oJ6!>x9Q~;AlkVzIRo8T+gjmA4bt&>CMV#Z=p206HuwemRKk0?cI;yq*_iHTtI@B$o9Ya@t?BUU% zcCWO!*f9mu12pSFitM9)48OxtXW7d=$iIcvbqFOv+7+i`W z9DPwEx{2$wv(Sz%aX=NM(| zE>QR?zdaZ(#U5bSf=cIgZ)(X6D9}!n%~twK9V(7V7(!m$ZaK785`6G}aRkRK8n+E~ z1)=AM6)y2swP5yD$L#t`A1BA)UlQISot9Zvjp^A|DFxCc=@k!q+1Te_tPLLO#$=f6 zZC1ZEM|N-%JQFTSXD(K_i@NQyK043-)_z;gK|mzhEn*$}J4iahWD}o}v2|ju5YGr? zf^V7a;YgVE8@!Hr#6GANj{%pacW`i9=N=R864W7_DNmiD&0K`LjKLTN)+$UN8=iL% z8UBFiPOf}>`8nh^H)uqTdfCb$d=h;eOaM=1vA6q-z98bkG-wjj>Baeg(t2ZAafl*; z*aH3V#rdgYM7^Lg3kkG*n=hwNtOqzh<7Qip6fA}xz@tD1(Pd(fOurDEJ%8wZ!bgUM zR)dGApVimAC1c^WpgYv=}@ z6jJKazoScA!1YrN{Nq$zle&*x-1Xh6<~}=hvC5b3t~a>OE-9s?#q}RYhQ3b?&se^& zCA?1lR%lU!?&A{#!biHoz*SAB6mtK1@FSkJ!@(5asbr_uO8Vrgu~q(XCs3$Tm6MDZ zGXhD!S<>S-B`g!49+s$z7Zw^9Zkd!#F=bQbN}h9KWuR()7DvkzN{gFws%(N`%1sUi zNU9*WsHyp!oBZT(J2NQ>$Y8Y={gh{~VG=7NHj1GZ)=*N~fntqtzt~$L=RYuXe4v!t zVDNGSa-s#3f~KY>*cCtI(;2YgAIub+XYB~NnqJoU$X>S*4CxnbGIdISyr?PLyX0?lv^SD@VaD?g zB=m!lb3aaRqTQK5xBvS{a0L*q^*-599C}2Lz2M;FoU45~55>e~z8Z~EjhkTf1w9Qt z!UjR$tk62)8&~5$c=7%C*Evnglp>Znh7TgLsC*OiNETUz@GkZq{3*-ePt#T(C+#dh zPM0h}FHtm*k!HK<=z);Ba$7H(=Fx<{U)v2Sq9m)ul|*&qMq; zeMIzlL_M9D(iQ9@R!&ajxl7C~LtNGp6^adHAfcmGZAs{J2KDR)mI(-%TwPqg^Krz= z#Kp%4mFEsmOhDMq9^Zp~lV;;2(WUVma`P39z0DpFV{L)w!MW>lcC?Eg^*}FAANl&w zb(qig<79(|f>jFOi)<_{$p!6=U@xs!n<>d0ytJ2>mIAqo0Y055tC>yES9K#KfEG87 zu|E7`JlODY7&O%tZVqq%}9K<$XU52OC%j86-rMiSrYJR zx+HQk(nf>ru?j^8uU#5Ln99C~IKoethll6QsXuk(E3DW+TJ^`<%=sUF+%aI5Hz#6! z7KeGOi-_oc9}gpHh+R%syD_Sod(A=ha^~fZe)ApPiMm(MEi4MwjkQ==Su>iYRp5xr z!aD0T*~RB-vWH3-48dsysw`HLa2S@LR*Y6L08fl1Uu*b7v{Y_`G(4x_iSyC%w{P+n zD@$d#uNM?Q-umpU77`PdoZ&zxEPM)@%bAca!Kc%RoME3Et}|lE2lMR8D%n+LtI;UW zdsFK^GrZn@8OWz};qXIsPzsK==gW*pd%8n_~X*R=hFx6GHi9mEnFK&v(NWNNcy*z$qgmzcDV_hqR=OVgUVOJ~;OC%)c=)e>E* z1y$qm@p14*R3b@y^X5%L!l>$xd!JbodqKjUQ?E`5-W?qU51vHX#JIR6zJ~iqtzZkS z+#Z{1t*9rDRUM~K=M6q3Im<^{bgJYhK4GNSBB~^it0<8&yV%lOANwNl&UE00tXyau z+lmS*L5d`yh6V#yjxwsCu5MyB^ELEZ@+9fdq7q&o-Fwf5omouHl$8V3WaAi3Yb!1A z%^y&_U<|C&5Mz#h(oUmxw-dhA&ed`JLaYoKMxweUyf(Fa>PkG0xDfKClghb28wrye zd(Qdbw+`h#hkClpuPVBtRz~%gr6|p+beGPm8&bUNaxzkR9THSO*ScSXtqR!n%d0Wx zS$tR~4%v&Ly3fM;;Nhd*pC1Adh|67VqrzcJFJ~5n*MLpspJzz}3XL5(Beeo8^_z`9 zUY&=thfrsKatUVCzQQHTp0k{=QRe8}_=D_xU)lP_3ue}^SUJ{wy{VB%e{o1+9bpMI zX(AWU_SX%#ia4y0w^i-i3V42ySt8_8SyWc^AY-&Ul=_C1n5x&5`fg~saEw^Wa0*E= z-=t++KE>Bhqj zLEP6&xRvr>r07C8JccTNb+)!tdK)##f1sgBMhdWV*qv2etg(j42K{3$^>44*fNFos z*Np)>Mk;euQvE4SQ)PDF@ z{NyYl0j0)BwaMF&lD#SWrI{(+YWsZ>jK;awzgn1WYJ4~5*D=H4a!PCQ z*f4S5%srfqOS&D{oQQ!-1yZ~ID(namcF+1Mk8#)MHwyyRPQQ5^TjZ4T#cz5_tF^#>fNJz9|^VEwX>-T2Tn;a|#oXO9q#(bx) z7u7#xz`iacfniv(K3b$I2*AW8eQSvgy!sP6jnCOsBR zDdr^eS{vaV_1%00HiqPLw<9RlySE*9hTZM1U&sfZQ*ExNzV37MY zhdsiI*pWw}!f?cXery|MU>d2nrRUo;*(N8ZxeQR_Ui^)(;U3w|Ze? zWyjmHGm-XfZ&dt32wWH&A!#_q4{bW#YCI!bc&311FDF-AmUGO*K*%6#thRS2@ypo@ zt#~zxyT$}vnzx?{3vJvzaJfBgJT~?uCSy{>Njr9#dAdvA@O>eLt+Ko{O72|R)3e#D z)3)Sl|GVCADFv{X|MEr+4f`F5k@sUW)ry`M*^MugWQ^#s5dO;nRIIm^531Sd5&q*= zpKQ+0jCR-V>3iZx=BXzQF;kD;>-kL^ZIsZZ&QS|ZGu1i-YXyse{3{!Iom!}V_s*T4^&)0wI`X}nHD@C*~ zlRD#<>#1L=NBOsBy*?OJ5Bn%uxm)+GuA{K`srb5vwQ||!-evJ!f9Bko_3@mfmu{_> z(8}YUP-2uyW-+h2KsDy+Atr5=I4fyjHnYB?qi5nXsrxGCg(hw9`A#%DQu1VL0@qkt zmWKWaZ#?t7F6L!nTdEpq_5Hf`MdkVZ?y&*y-H50I@`9z)q?5jb1MIFHT|CNFx|JLF zC%=@|+yAoj1`zPiW)o{BSFeqon_NAX1F?9tKL3F5wqB)47uX}$t&WV0G|o2NLX2Bt zuSH?A6+6A5*SHrEEBL`C;8V3i9tGunK33BuUg0-RL^)Ok&HhMKq;qo0Ug;()!|&cZ zQZA`B;-pNfs2o)t=cEn){pDc6jRVFXQ*j0S=N@Orj?RyT-Y0lG-t4M6X}4lkio$BU z>h?>@!J$%DTKNv&r=}*6K%~GA9KS5<7Xq?NQaP3nvSqSz;(dD`S1*W2trqDAFB_r~ zQ0KwVOjE5>4lhp49`~+CYYrsyHl&xw68sUcjLWTTkZ$CC1iyUXQ1xp_9^2Ern(%~k zYM%XkY&ly~YbBqWm6erJ-P#t|s}XcA)F=i$Z=%3N8;SYnAGIbwH4NNn3{a9%rvGA> zO^CXCBbgxW_Jh4AlS4(l8`Jq6FU?Get%`%!m=nb{6#G4h1y?>*GRfx$` z`ak{nq=A!PMfW;0#K;n-k0&oOtZ#$X-5pnTvy0i=+Oya4X^_T7MXzd=OjnIar{Jnc zXON?F2fKXufQtKx%FpWz*jVQ8IKy+$LZ#?kb3j^{eu?z&sc7S6R<(BCk8$SE2$UjO zGs3HUY}lQUX{3%vq%OjmNI|eE|GcrZDAGtfjtR+>y~!k0^}n$IX2Z&xH_UjaDMCWC zQLoLlB{|KblkYyNFIscu-NSR(IM`MZ6M7JvpF=_v5@r5}Qb*q6VPF$6*Zz+bxo zv?Zn?K2!T9b4~8BP5iG117qjyq$N?!*xTzja0scSV&fdxY8yUptX3?GB=OZ0C6XxvqD*g)iybe zwbZh85?TDJMxfXbBFmomyEyB!66BV=Z(Y-QYEwz9UQl!iAtgf@@g9arrJFr}U`3aI z1BdB}QzT~Ctw5PbWS@))d4gn|3Xf3u8yRgPIw@MI3KnHu(fqvti$sx@A90dTdU#1R zq{5giSof z5yjp}Lh_#Ic>cuWZwL{=Wc~G%&N3}ToPe&&J=9m=+%EXh{TtpM95?9_BdDJQm%g;& zNf8PlpTJni&kn$8ZQZIj%EUrsdDbDrpElNa3Ha+PC@2^h%mIyHS4KE=Qyf|Ijy?0- z(u%j}`*JRB@DtqFguu|HzsQ7d#&FVK^hHv;DuC?w z*_a|Iz4B3Bw|RzvK6mJrLP)7$YX+6hs>9^Sp+9wq;kzzA%Kija)i+NcnAVCT@!%}K zGD+iH_KRx{ZU=0a6kIqTkMDy4!j2i?{!_#Pj zop*=zVk8LKrr7pJ{xbd%0kQ6`_eoBbPB(HI=G>R{L$$rdAihlRvs=q!hrjIN;{Ec*aBXv^*cLV)8;xi_ui$6lu z1Y?KoYY^zLVZ(WC30jCN444Ac7_>YRoD5pD75^F^)xH)(pMS!FyKy#Pl?(tPIK=$h zIPHzdk#Kzh1$cJo-v9?L6>{R3T0RVm*2rj>9U+M+V2H#{!6C4a z2z@rK5!`_5EfK!Bk+=%L5RsTJ7+2vmTmqT+s|b1uOqUC+^#!w+9MO?}L;j0O+ zD+pxe|M`$#7F0&iSE7{{H(E7?w)3NX?SdozyDgW&$WqD=8uY&xvjU!VV;~6e|EG<( zp^akNp^f0c#K&}bg#ACy7}^vA@fl}E6$7b~<#Wpoz7P?*Q;@W=iy*{1U$+e;8~xWK z5zt$O=L4Lmp%<6lL?9R8J28nckv=*8@7u7T|CfC7s(9!EcGDGvxRn3@`kFo%OeJjr zZ3EB$_f_B8GRXC=s8~4|I>cLbPDB~r+rTlfe-vCoDs>^ ziXPO}n7%>?dgAt~lAAX}#H>N(VNi6$df~e&Ii|o&c3bJdbSbTR?lA1?IbNO|W$NY* zF9ehT!6SWyz!wbL0O>Kr5=e`ydB@t@lBDGDa%Lz?eCX%uDr^?FR8%0h_^y0;?w_tv ztl#R>toSI*<3yv@Az(dbP22zlAE%58zKo{%cJ=SZ&EFc)73G*XP*@zIqD$t$H#~zs zkL1WMt2oQvw7e3PqIDG`I2L=oopN+p2RA7PtHOOKV{zWQ_qvbkjTiAmf2u&tC0@?3 zrTuf*OBfjmy3+2Mx%`iRP@ngV2G{VR;2M4=7H)QAB*8_p9{1`t>B>^3t`Yn1b6gx> zob-#YbNiI9oPUn{_MV-2br$D{r~c(Adw0~R+;;tKW7Er7nwtr)Jl5Nu3d6KzpL0v> z3mrA4ZQIo|{4o4&SOZ|k^Rzx79Gs36Hml*y#b-0C98JTWW6YU{b zI~;c06rC=5%dg+uIJ%aM2#)Bzs^2Yb|C6=#TCW%}JL?A1h4bf3H@~wx?C!TFB-C)u zFb#F|Y{=eek_mjyuBzghSiN#3&{#Pn>VeSh8>AFAHX%`;1j$LW$tzi&NU|_uqLBCL zW$Bn@KF2Rx*8rw+b*Y=svu_2%=XnS%JN6d-3e!qz8z;}{?AMj;=#Y*s7eRkA|Mu$& zW#Y@>n0y_S8^67ZyNq(NE@X1#MM^TYIO*{Os3<#K>>PT(OUU3fEt1R~TUAwP`cpfJ z#4^00zKK&<0*fTQ{U;H!amnRtp82JpUgY+FR5Xc`Vpz)gPO?=h)4Q!tUus?-bO&?w z=T9sPKN8%R3Jitk4$uEvlQYiEe0(02wUd#Op8Smz|IKP!lm4P|lzfwaDr%s6#pnaU z!K;90^z`aPiU)$IXU{n9-J{3NDJUrT^yvULGWHi}y%RKw$6nNc&XCNrXV(wv5v@jq zCzV~Vo;qAukxn36FZp60Bb?iJeLu=rv0{2PI(Qw646DC+ZaAXWx%M8{n( z=i{@6zapmH=Bl*0$TIux5z7LKFVjUMDunmN>1Zg~91d}g+18}gUeGZxT?^6UrE)j`f_MuZTkHUy1ZJU1I^Sw z@;B(X24EjTWBT=Rj6RHzn-Xed6tcRuhU0H#Ddj)s3I!^~Px{oAIr>Ib>S#qc(wiB( z$GFo>_;Zt9qrUlN+lT&H=f#q7r1Wpg-2J$LKeONHHGOO<^&U(F&WIh?Sg(yh;7sR8 zc%T(HP_5PSfnS|MDsHt{?a?&;44sO4Q`i+5NzzYOIh$UH3g}4 z2RMWke_iw0`MbBMydT;; zWKk(pCj2U~UE!l;bwe_9!LTL$st-!=C^fM5RqlDtoj5X&VpHE5Q+Y2nU*nIX4x%6G z(8olrtF{4_%i`3bc=w2Ug%C}Pee+z}f)g0e8g+9+0o?8ZaePl8@*W3Av(uW2hDOr7 zLjUy}A&(jVZtHJ#@fQzlq!lyZ@^R~Y=aS7M@f_(x#uNPNutq19F7h3N))aJJRq zm+)O-n#(jbmOn+y6eJ83nJk%akhj?Aa+v~gE{hmf@}%6KL{6+N)I9Nw^4oQpDU}aY zi#}2Ml-1O91~7XXUW-UwQz?>bSUy?f>*tOJcHF7`Xh&WsZ_oRj+FmxxeSBB;+ulx( z5oLDwo*K{n$4KfME0ext)3?cVsFSB|TL>4$33l7BI+Hf`VR{8tv)&BcwPlP9+Bs~* zy^X&pW^5470e&0cp5-hw3wm~ezJaVH*KSV-Np=&Hi#m<fjJ5=c^Kd^-i|xJSTp zp+ixgEdBTCGT|ZjbS73=x&n{ZTD;}KNxS{9`9r`nfxd<$EO-+nV&0k4^Pk2>X_m$=Lg7gq(hZWrP+u z$iNo4w~vX}OuC|zQ+3-N2SK0c4Gz+X?ZKXB*Z~+~O2uP79ucSWj~ohqEbKA1=XCoB zTv)pDuBn;aQ{Hoor^nsX&d)DAP(FN&wY0B(-NHI`_LzBNyOqLW{Ak6_x2fDK4o794 za+He3lGU$>;uqdWM7fsrsbXEP@V)7;n-zXF>)Tv`1H-SETg5%symD{*!wKIHo2w`y%{DirfvmEwq4_T*Gr zevXj~51#1E?4sEMXqU-fvq884(cSZP(cNpUu>s435n92AcOv4t)CwZyiS(1RmxVI6 zE+4Z$K07na(fhvNoH6TOIWlhjYhS>ne3pV{*{ zBKy|S!o|3R9#x7EqlO9A_Y5Q7<`9}RoDLR|tsgu%5{sBMzE~5(=@)Dc3a~iT zq@sw??D?n0-951w{d}R#|H3nZ`?uSftXubpqSObeQ1M4~;!Gq~xfBg{lc@ci`f@VI?lha=f3T|aYBUbX8>FH%y4 zFq<@1wzo?QI}k}vXrkBR$HgkEV*85oftO3wR?m!tLSvM})eYLFe{?LUpp+pl)NvCA zwz~%VVbPzv&>@c!-$WPsl04uJkGxe+sqyEBc>>AUGn&1R@y=UY{G?^}42P(>fqB;z zHw4??YVWSj&ZCR8nbDb<$U8?2rmnwbt!oEksAC!PL;YX9%1Q{`;`rd0EGZe*XhSyr z-D=*kp?wV%x~jKx6mylS=*@RcS(TRbFC;5%H-s`4uI)zSxP>)V?&5r${3c2|SH3e& zb1mqgi{pg$OcQB0c9*Q-k(G1B2u>096GD~t%@1;p4*5m;PlX#O?pOco-b$|hO&rj3 zLQjrQ=#(u+`dRVy?For)Vm4!J@5C7Kd1NH-;7tHZwtXxnL)D$V4Oi-I7pI^ zn3$M~>J)bS-NjPwi3rhLI?V18&>)G~m`{)GVi}UmsdQ6>ZPH8lqBrec`IJ+~3?Ay^ z-3_^Ur^Rj8L{)Vp2wx0m9jt#~yMg^MzX!IL<-zg|&^5akTs_YBt_4XReY!qVK?AqC zW^D=Mcp2x>&5e6bzcN~!FJ8&%ypkvGuU3{p#o{d)Bn?7L)NNIXJGsHH|MY32_Fxqw z)B3pWvLr4=iBtN(j3LKz24ohTZCat+3k{5k^j$spcWz>+$gottt1!ItWif7a!=dHQ z_Hbz%mdns?V9L<8o1Zw)nB0L=gVo};_j?%J?vf8*_Rb2{KiKqJ{}YI*V)0R%`QP$> z8;3~PqbQa)*T z<0#-0CP=lb44gLZc)L{X?oMO>&GZ+tQS_=3zn_rz+8W(Wn7QmxvMUOSVT^Bf2p67h z3Z;cN-C<(Jy9hsK@1qo!UyaVV?{t>tP1kizBgfub zwsAllm?*_v%Xy?3_ct5yUvtv_cKiH9QKIK_5WZ%q4%69OfcYdK4!6KX6EJW5 zsQ?_{q@yz)eXq=G@$L7C2`ANJPQFtYQ;&(MX`@ea!5SJM48DP+hmk#ILiRB4T*X2A z(BjfE+yel!mm$)6W4Gekh1Zye`$PvjM@ph z?7Z#wnMH`oGCt35cKt3C5n%4wbp7fa1fK81R^rtBt_c5j^MWeT9xUvjkU&jMeJ}AI z9UYyhs5fAOXuj!$MBhy-0m*aV^LR9008aep(e84l#~;?Svo~~)0|m-}Qy>C9xBwR1 zIw?i7xX_$Lk2q^XyCA8VDAn;n%wXdTFjLHM>CLw<_*C0=Ji^IG(j|xqQSH6(dcg5qo2)`g7yo5 z)adZmR#Ni&hsnt5B$N!9DmXeRnl_^yXrV@icK15FTYr#0zK#YG0}k>-Nl8gr*?<{O z(bylb+k)aPgg={Ls=B(mP=CObDD*Ps{4+k6eH0rL(@%g;r14pO2eez=ejAJs@9CDh zvSBkG@4extPor%#s;h!ulnh2 zw1cICg9GGK_Umt9F1T+e@AT|h)SK};5T%1>;zG-9QI9>C5y2@tE-uaoR(5D*hCmGf zQttg$z7QpsW zx=p*Iy)VxXchkpUjJR%2Im4A!LNImZzDoET!moCpSUZ)kIV9{5+vCe;?+_*AH33ib6F8yz zOv2IuV7kl;aJGdPK6`uMk}Wc^mS2I5_#0Wvgk@me482{C3KC1V!W1aoCYk&GOP!|; zgbIlmOwn|YgK@=d=Kr|)>E`5=oj(TQ*i!>nbzR*?vmT-?xxy8J7VD?D;Ajo)11!PV zQw>wo+&A%)t-hB)ivd1cNbAa0f7%q;;g!H4fQHyF=UAP9;b2=7J{_Oekv-iu%k4wL zMIG?MgW0Xg^U$tdk8|fMBMNu&&UYJUo8!H2FCCvUzE5xDd@Ut=txE})?gtGMoiyVz zvZY+>5X9tFSvfg_-w${~NBDv^cLe^LU2Fc>3z&XtfP&x}GZ`YQa)C_43UA(UbJ?D2 zHfnzj7NBP4<}e{Gt~zvgTuRgm(QL`N3THrn81$u%nYA-)aAF!vsYN~beCA5yM$;ZTHbnhFN}_pdFcYU`INd`*Vz~mm zVXB9(%$_{?0&^k^wtoO?q89T>YrY6Da6|nO1Cv+yRcP+W7S_c?Kv`a1C+mks_`T2< z2fXV|yKh`fd0xR|N7Wy3a-#G%%qTa%!hl~p{kQqE|6Z(!boyJGOddg2XPH-1I~P}< z7MR~t-;Jzza8jO_*RVW&TD*V>i#KjHBt0;m7Q0~xAN|X`{FZsQ()ZG5YZsX0vqC&h zBF2%8PRDHe#$aV;*ysYL-y9d;&3GI*EXO{m13qcF)eR7)GuPhZX2Xtw9aV`=DLOxI za9#(?%D{DDo_8DNFtLL{=(pxzxp$765v?1aeJ}pqDKy;z?kq5Y&K7Y1*ScVb>m6k5 zGvMD03^B;JE&&xB9~UPRm&fua!*gr0(Ut4)%7sy!K`dr$iIb`9h<|S4{O#t0!sz6< z-SI*rEo?&ixJZ|mX@<2&0t#+L{1f++2JbR>h_ayi(}k1QE5OG79GdtR3CYH6V=|Rk z^Q)h-g+IEvP^F;i11Ua;!G6J6{G*H}kut=HeBQo>m6Ug|Iei#mct+39?*b@4z)gM` zJZ)lBhsn=!n0~7dyhtr;rkvM**a9&bh&&4!hem2SJ8=n?;JY4p$y9 z_}ASRn3;VTEEf)VGLQ59@m*d$0e+7?OCN*VbZYwgn|#BAW<1o7yxyM1t&@L=rcO;t z`mxV0{-TkWJW^?RMuQ0Lt1KG3w zUr&GkEFh9)duyhC1Hd1aKwgi%-^qqPlL7DQy~`jbewO%}7-Dh;DNj$~@f-us%?Caj z^EUkDIyJp(`!n_|{#na-=5y>`gSI(sKeH=gbzH_tOdNN~`(<%6P1G~Va9hy%&nUVN zm978s1&hNOQ@gV7Fp?m^p4#V|^ZNH}>;5>RKL2B#V3;xaR);7Dhb_qZcgNhB2MRY( z=E~8vhn1dqu$H!qx^pW!)8umF<6uLJ5j=I^8XoOJgfuI3R3~9)_>Hkc1|^9iB#K+$ zDt*zBiDIOSK78Rg*vM>H%ZWY1dW@Hl7idKOD>vrR(hc;A@R!$-L>dhTf@vv3%(n#x zCuS#3%^n>Y1uk&04;b!MRaQD&oE^I<2+l*{H_$8EUb1x=BV+Mb<1UwHN|tSwD*fGOg%!yt*x!icnz{|#ob0Y6{iE;lxwoR2#?gsw3kHZu>jBd^839=hzYn+Z=Rg34FLmpb9^CXj>Hr|w z(eeYdgF37244CR82`eksWa?EOjF21DubPLfb#Qu7;lLR2Eu-Femk_b-iJ+s5-C*!SKLL9?2y1|W=4dh9@pWV9^@g%;YUMHaHZuo~c zZq4<)aD2P*`Uer^?RittzPqP6!8kKT?(g~n;}Vc(yG!~EM%&V_Q1=8W(0w8_Jlq3p zVs}i_6=0>QwH(d?q^JYe*ZzL}N)xQs{R^1+NPW&A?9}sg&5V}BH9ecoh7s%wd%!^3 zQ#CfG^MU2d74Y{*_fc_Z_O*x5z*LR3!*fRqb?0Iy1V~dUmkdJb(V5H=xc!-8nPQJMXbGif z1iwZxVs_A0txmhkGUxc{IEShktPe-=KdtYX8i^4^g*tB~Wl8)unijUz8UZT}${ag) zdELv)6Y|dPd$Nk-lL$Nfaf2G7Z=Lvf^eW^(>j#2*nf}5wB9s<@xB$HlX)$M!7|{cA z--GufPx=3pF1KfT16g(yR<2zXDG7;Qi`OyOTD1UEm?gYwdK1VyFyVfKU+fDh>dA>m zkfZ}Nt6j41hHUwG?b!<0HlwLsJ!UClz84_tm>~O;81VZLt+?9G;bucpe*wY}#9>k| zYEA$Kegb^e2qcv7l)Sw~1ujM(K;_DilaFAtW?6zDhr=ZhS(L!@giZ3RrQ@jE@GfFd z{w=pLJh++fcxPW8xNK+@%GiMpBsg@VpS{3a0y;jZdKG4PgzJ8kWh838I9Uf=xuv%D zyB@QuNt+`(UHVuXpaH|=;{nfleSJM$!4$yzH%0f}j0{Laztt=rJBoYcu!L!62wxGn z2;Li&-8CYfCvP#B*DSK zz!|c;d<2P`G_FI?2jJpZn48}ijzW%ub{WvwXw7x1D?CHdP7Bz)V3yX{c>YlLh zw196k4GT#)uvX5YjM`?%2_o~5xkD03`N+%d$rJ2nBh2wVmVA^1!RY=BGtyzw6?g48 zNlZHFuV1Qe_*^K+$Et)^8YF+fZU3KX8`DJu5*l>00|{rDe&dmE4q9U5%%Mb4?0gTB z7Xa!9B460r-U3C9mZ4oKcCH8sv{uWnf%HD#C#4!rJ^q#Y=@WmET5ox_3}YOqSPZ5LY(p#nCqfzxC(0`-Dk>;!xkNbB z>sGtiET@DAI} zfkN;3rTCu;X|gJSye};&nW=LyRb*lnNqAN~=Hl(D7nfs|LKP19wC&|@Se&TEYWUZG z;q)_R)$tN{^r?f(|B-#DIZ?agWscu}{CJ|AQ2wQ3(H5)jBL|?Khu@bQH2?Xb@~!0_ z3WY1x%NW@eJ)V`jl=JQ+lMUM-DetV>*kUL}$VsHkfm%^dZw8huw9v|OYAbByxawaR zWGjIj%otFnHeLMRNfHQU*n)Rs zfz>R=E(iTMN*=SXXcs7$>RSpE-vR69p`EldOO>38fpb1^PnMM_hj#0JjqY}KcJ2we zj0IfZhmb*no9L@5y5i#!(W6wLb`Oohhy1+Rz))4yv{DQ3?yylgZk;Mu%n-=K%lq-; zM_wKtpkK-6hlhnJ7pV;o4+~QHX98mjJ&V%>qE(>&J!d{1>t|TvT{257ZH~EKZoTQX zoO_3CP83^nM2b;133)AC9Ji8P*U(^E$=S9>SB0{pZluoVzM8kS#^EGBz@U82Jr+Z* zNJ4aQq!pT%mj}8?8)(kE_x>#d(|4D$P&cz2%7RQr40M1*&nor9CgKu8{L>Y;{$(xu zN8>mS@7f#nmxku36nRW(QPVLJg+Z467ieTqgmHk5C&@2C7A6H8E1f2{O0|2=Y{i?+ z0;^Fe=Ul79p0hmKo7>xh!rRg8VNxqbN>M@5q4_LJql-2I;Y62Q0}^vlXqZ8&Gbu0Y=qKH8x2)w|VwoJ=#mR*&1J#QQ z?h@a<4dt-ROlzXQCXWkkPc@S*=6SR@Tlt$}3bf4=CfrKN=vcHxmIo*ct$z{p$=K60 zwYTF@6>?nnJG8=w+x^(o!u>WISB&qS!x(h-=96FBfFaZjzB+dk`x|{zJ<>j%jwQP^ zZy25EACT6s`Q4xC>$pFli_PZq!sPz_Ycnk}m7h!X+y-dJM6~vVWwIEtm{{dyP`$OV zJ4#!Gw1C+80g&%kH6pKir-#z>pWA)&PK~b7!Q8w)k|Xf)^6z)i`VMm*JV}{*grMGw zqAQ|Wwmj{Y!Kb0wK6&7ik}&S-z~IqLAwh$k#W+?dr_!#SMIJ~wv)d_+cYV*H+mwHL zCwb0EgkFObX;LJvc~T%kQxl(7Dl`8$ltf$qCE!pNq&CpMiHGgLmNGcrmZ@-#TV_gMYQ+H?Q4i+G52 z$z6NhTMCk&7!xxyxz7APOAKBsBYr`BGrY-Iyso^zukW#s^#N_@cjA@lWQ8=|dxUt? z@$FDv!!99vk7L1u1j~Nb9Iz z2>eY;yDcJ(=aIa_Kp0;>Blc&u%M|Cm*C%psi?p-8yUb#at4Kno>g}aC6-e24#;gJ{ z-b)1)BiT`l{J;Ojx_~9fk;p;@vtE!)cSi`X7UAp6>VP<Kyz(7D*nxi>V^{WObZ+?HN1zCDjo6uO*wCg3{RH1nWT1Ana{ zW1;N&b{Xa~uCqV|)U|oNW7Db|?boJlbBm$evM5ZYWy`if!5*!uenRGAQ1)Wm`(1bD zeM>KoZz&HuLKu6y=x@HHgAG`Rn`+wHYmb-QK?#+=Yj%e5I4Ytea5!S#Rk@$_3clJ@$}IIgyk?!t!9 zP;Ig5B* zH10j7Xnop#{kqUwnGS9n&%)eUOI|H6aE%+@VKeHw)SZE_SQ1(C;XQZtf=D$&n3_(RhmnC0H;8?>%>ap8o4u> zGVbd)sP{gys8=O?+DuXEGC7@(Qq4-CmIqw=@-m=4$~0EWr;IlUz z88*u?ngVrIeSCZX7K8rbFJ~K^i;9ZcAC)7eky21d1@%{814o<+o(WE@Vx39fqvsv{ ziWa8Rd;4L&ZO`HlnHh9^a#f2JLR;NEP`|?4uR3_&wj-oLm)pc zj)+rE0QO%(ZJ*gK-rb}%qg zJe<*9Lt&f>{|+|k5!Ya1IQM(M1uIp~Us#U!GsWgar8JNP=cFr-AHAawN&d5Nah$dD z2g4_yz(ja~SfPXN%=O_hZN;*M^1=kv-twk#2Saoj>rW-_(4WUQHf@v!?CZLU+LC$S zx+vW6_%L+qVLKsBvNuMSW*s42t1TbRT$%+AHf`UP&*Wlxk1txoy^s^eXFVFf3+F^f zh3E?3K0fNN)V;4k?iKm!U0>Pd>k9+*8!b>I*{i;pr5IZskEJYt7>MDR$y zMt6QJ8!z|$Zoum@gI_no!piDDdIdp?Sx<9g@t;S6^+n70wHiJ7P?A|lR&F$Ye_&<+HG@JjGwge#V{pw#>gG@#b6jaWr~qmj1Dnqm`L7;egh?~AXO%5^!on+{b6r7 From 160948535d1700fa64e1bdde9c168d1f8306ed87 Mon Sep 17 00:00:00 2001 From: Kelly-Ann Dolor Date: Wed, 30 Apr 2025 13:32:29 -0700 Subject: [PATCH 06/31] Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- docs/blog/index.md | 9 --------- mkdocs.yml | 7 +++++++ 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/docs/blog/index.md b/docs/blog/index.md index a65b8387dc8..90a1400c6fe 100644 --- a/docs/blog/index.md +++ b/docs/blog/index.md @@ -1,4 +1,3 @@ -<<<<<<< HEAD --- hide: @@ -27,11 +26,3 @@ title: Blog --> The official source for Apache Sedona news, technical insights, release updates, and best practices in large-scale spatial data management. -======= -# Apache Sedona Blog -<<<<<<< HEAD ->>>>>>> 97cd25dc74 ([DOCS] adding blog post, fixing blog post formatting, adding blog options) -======= - -The official source for Apache Sedona news, technical insights, release updates, and best practices in large-scale spatial data management. ->>>>>>> f2bbc6f3ce ([DOCS] fixes in formatting and concepts) diff --git a/mkdocs.yml b/mkdocs.yml index d55aefa95f7..280d91903bf 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -243,6 +243,7 @@ plugins: # prebuild_index: true - macros - blog: +<<<<<<< HEAD <<<<<<< HEAD # Format for displaying the date of blog posts (e.g., "full" for full date format) post_date_format: full @@ -253,9 +254,15 @@ plugins: # Maximum number of authors to display in the post excerpt post_excerpt_max_authors: 5 ======= +======= + # Format for displaying the date of blog posts (e.g., "full" for full date format) +>>>>>>> 8dbe11ae96 (Apply suggestions from code review) post_date_format: full + # Whether to include a table of contents (TOC) for blog posts blog_toc: true + # Format for displaying dates in the blog archive (e.g., "MMMM yyyy" for "January 2023") archive_date_format: MMMM yyyy + # Maximum number of authors to display in the post excerpt post_excerpt_max_authors: 5 >>>>>>> 97cd25dc74 ([DOCS] adding blog post, fixing blog post formatting, adding blog options) From 1faf206ea98c471eba0701992d086ac6972e2bd3 Mon Sep 17 00:00:00 2001 From: Kelly-Ann Dolor Date: Thu, 1 May 2025 11:13:56 -0700 Subject: [PATCH 07/31] [DOCS] putting metadata at top --- .../posts/spatial-tables-data-lakehouses.md | 22 +++++++++++++++++++ mkdocs.yml | 7 ------ 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/docs/blog/posts/spatial-tables-data-lakehouses.md b/docs/blog/posts/spatial-tables-data-lakehouses.md index 379c9c0e681..41917f346b9 100644 --- a/docs/blog/posts/spatial-tables-data-lakehouses.md +++ b/docs/blog/posts/spatial-tables-data-lakehouses.md @@ -8,6 +8,28 @@ authors: --- +<<<<<<< HEAD +======= + + +>>>>>>> 14074d417b ([DOCS] putting metadata at top) # Geospatial Data on Iceberg: The Lakehouse Advantage This post delves into the benefits of Lakehouse architecture for spatial tables diff --git a/mkdocs.yml b/mkdocs.yml index 280d91903bf..c0da70ac7e8 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -243,8 +243,6 @@ plugins: # prebuild_index: true - macros - blog: -<<<<<<< HEAD -<<<<<<< HEAD # Format for displaying the date of blog posts (e.g., "full" for full date format) post_date_format: full # Whether to include a table of contents (TOC) for blog posts @@ -253,10 +251,7 @@ plugins: archive_date_format: MMMM yyyy # Maximum number of authors to display in the post excerpt post_excerpt_max_authors: 5 -======= -======= # Format for displaying the date of blog posts (e.g., "full" for full date format) ->>>>>>> 8dbe11ae96 (Apply suggestions from code review) post_date_format: full # Whether to include a table of contents (TOC) for blog posts blog_toc: true @@ -264,8 +259,6 @@ plugins: archive_date_format: MMMM yyyy # Maximum number of authors to display in the post excerpt post_excerpt_max_authors: 5 - ->>>>>>> 97cd25dc74 ([DOCS] adding blog post, fixing blog post formatting, adding blog options) - git-revision-date-localized: type: datetime - mike: From aa81cbeee79bea634f6e0009653d005a2ee46312 Mon Sep 17 00:00:00 2001 From: Kelly-Ann Dolor Date: Mon, 5 May 2025 18:11:57 -0700 Subject: [PATCH 08/31] DOCS pre-commit changes --- .pre-commit-config.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 7c4e772e952..0f69f494e80 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -79,7 +79,11 @@ repos: - --license-filepath - .github/workflows/license-templates/LICENSE.txt - --fuzzy-match-generates-todo +<<<<<<< HEAD exclude: ^docs/index\.md$|^\.github/pull_request_template\.md$|\.github/issue_template\.md$|^docs/blog/.*\.md$ +======= + exclude: ^docs/index\.md$|^\.github/pull_request_template\.md$|\.github/issue_template\.md$|^docs/blog/posts/.*\.md$ +>>>>>>> 787baef7a3 (DOCS pre-commit changes) - id: insert-license name: add license for all Makefile files files: ^Makefile$ From fdf203985d595df3250466acf2ced05e56908917 Mon Sep 17 00:00:00 2001 From: Kelly-Ann Dolor Date: Tue, 20 May 2025 16:14:18 -0700 Subject: [PATCH 09/31] adding single table transaction section --- .../posts/spatial-tables-data-lakehouses.md | 27 +++++++++++++++---- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/docs/blog/posts/spatial-tables-data-lakehouses.md b/docs/blog/posts/spatial-tables-data-lakehouses.md index 41917f346b9..fb196f9dc53 100644 --- a/docs/blog/posts/spatial-tables-data-lakehouses.md +++ b/docs/blog/posts/spatial-tables-data-lakehouses.md @@ -59,7 +59,7 @@ Tables in Lakehouses are governed by a catalog. These catalogs don't store files Example Lakehouse catalogs include Databricks' Unity catalog and the open source Apache Polaris. - The catalogs allow for role-based access control (RBAC) at the tabular level and features like single-table transactions, which ensure that relevant changes to dependent tables are applied atomically and without manual intervention. +The catalogs allow for role-based access control (RBAC) at the tabular level and features like single-table transactions, which ensure that relevant changes to dependent tables are applied atomically and without manual intervention. You can query tables in the Lakehouse Architecture for business intelligence (BI) reporting, data science, machine learning, and other complex analyses. @@ -86,12 +86,29 @@ These tables are interdependent: The `store_id` field is in `stores` and `sales_ The `stores` table is indirectly linked to `sales_performance` via the `sales_territories` table. -**Impact:** With Single-table transactions, the updates to the `stores`, `sales_territories`, and `sales_performance` tables are bundled together. This ensures that all of these related changes (store status, territory boundaries/assignments, sales targets) are completed successfully and become visible at the same time, maintaining accurate and consistent operational data for sales management and analysis. +**Impact:** With **single-table transactions**, as featured in many lakehouses, each update to an individual table is atomic (all-or-nothing). In the aforementioned scenario, when a store is closed: -Without the atomicity of reliable transactions, if the territory updates fail after the store is marked closed, the -company might have inconsistent data showing a closed store still linked to its old territory, or incorrect sales targets. +The operation to update the store's status in the `stores` table is completed as one atomic transaction on that table. -Let's see how Lakehouses differ from data lakes. +Any corresponding changes to the `sales_territories` table (e.g., altering polygon boundaries or updating `store_id` associations) would be a separate atomic transaction on the `sales_territories` table. +Similarly, updates to the `sales_performance` table (e.g., adjusting sales targets linked to the `territory_id`) would be performed as an atomic transaction on that specific table. + +This individual atomicity ensures that each table remains consistent after its specific update. For example, the `stores` table won't be left in a partially updated state. + +To ensure data consistency across all three tables (`stores`, `sales_territories`, and `sales_performance`) for the entire +business operation of closing a store, these individual atomic operations on each table would typically be executed in sequence. + +While single-table transactions don't automatically "package" these three distinct table updates into a single overarching transaction that +makes them all visible simultaneously (that would require multi-table transaction capabilities), they are foundational. + +By ensuring each step is completed successfully and atomically, the overall process is far more reliable. + +If an update to `sales_territories` were to fail, the `stores` table (from its preceding successful transaction) would remain consistent, and the `sales_territories` table would roll back its own failed changes, preventing corruption within that table. + +Developers could implement application logic or an orchestration layer (like Apache Airflow) separately in order to manage +the overall consistency across tables, potentially by handling compensating transactions if a later step fails. + +Now, let's see how Lakehouses differ from Data Lakes. ## Lakehouses vs. Data Lakes From c8a32376365535db18fd2df7e9d5c44e8408840d Mon Sep 17 00:00:00 2001 From: Kelly-Ann Dolor Date: Thu, 22 May 2025 16:53:23 -0700 Subject: [PATCH 10/31] blog refinements --- docs/blog/index.md | 7 ++ .../posts/spatial-tables-data-lakehouses.md | 68 +++++++++++++++---- 2 files changed, 62 insertions(+), 13 deletions(-) diff --git a/docs/blog/index.md b/docs/blog/index.md index 90a1400c6fe..537588e5e0f 100644 --- a/docs/blog/index.md +++ b/docs/blog/index.md @@ -1,5 +1,6 @@ --- hide: +<<<<<<< HEAD - navigation @@ -24,5 +25,11 @@ title: Blog # specific language governing permissions and limitations # under the License. --> +======= + - navigation +--- + +# Blog +>>>>>>> 3128e022f4 (blog refinements) The official source for Apache Sedona news, technical insights, release updates, and best practices in large-scale spatial data management. diff --git a/docs/blog/posts/spatial-tables-data-lakehouses.md b/docs/blog/posts/spatial-tables-data-lakehouses.md index fb196f9dc53..87d8f86b37f 100644 --- a/docs/blog/posts/spatial-tables-data-lakehouses.md +++ b/docs/blog/posts/spatial-tables-data-lakehouses.md @@ -5,7 +5,7 @@ authors: - matt_powers - kelly - +title: Geospatial Data on Iceberg - The Lakehouse Advantage --- <<<<<<< HEAD @@ -29,39 +29,51 @@ authors: under the License. --> +<<<<<<< HEAD >>>>>>> 14074d417b ([DOCS] putting metadata at top) # Geospatial Data on Iceberg: The Lakehouse Advantage This post delves into the benefits of Lakehouse architecture for spatial tables and differentiate its approach from standard data warehouses and data lakes. +======= +This post discusses the benefits of Lakehouse architecture for spatial +tables, comparing the Lakehouse approach to standard data warehouses and data lakes. +>>>>>>> 3128e022f4 (blog refinements) While spatial data requires different types of metadata and optimizations, it doesn't require entirely different file formats. Recent advancements, specifically the addition of native geometry/geography types to Apache Parquet and the Apache Iceberg V3 specification, now enable the spatial data community -to fully integrate with and take advantage of Lakehouse architectures, overcoming previous -ecosystem fragmentation. +to fully integrate with Lakehouse architectures. Many of the benefits that Lakehouses provide for tabular data also apply to spatial data, including: * **Versioned data:** Lakehouses automatically track changes to spatial features over time, creating distinct versions ideal for historical analysis and auditing how geometries or attributes evolved. -* **Time travel:** This capability allows querying spatial data (like features, boundaries, or locations) exactly as that data existed at a specific prior time or version, crucial for reproducibility and auditing historical spatial relationships. +* **Time travel:** Versioning lets you query spatial data (like features, boundaries, or locations) exactly as that data existed at a specific prior time, crucial for understanding historical spatial analysis. * **Schema enforcement:** Lakehouses enforce schemas to ensure spatial data consistency by guaranteeing correct geometry types and attribute formats, which improve data quality and query reliability. -* **Database Optimizations:** Techniques like geographic partitioning, data skipping using bounding boxes, and columnar storage accelerate spatial queries and improve storage efficiency within the Lakehouse. +* **Database Optimizations:** Techniques like geographic partitioning, data skipping using bounding boxes, and columnar storage accelerate spatial queries and improve storage efficiency within Lakehouse. -## Data Lakehouse Architecture Overview +## Data Lakehouse architecture overview -Lakehouse architectures use open table formats like Apache Iceberg, Delta Lake, or Hudi to manage data stored on underlying platforms like cloud object storage (e.g. Amazon S3, or Google Cloud Storage). +Lakehouse architectures use open table formats like Apache Iceberg, Delta Lake, or Hudi to manage data +stored on underlying platforms like cloud object storage (for example Amazon S3 or Google Cloud Storage). -Tables in Lakehouses are governed by a catalog. These catalogs don't store files–files are stored in cloud object storage. Rather, catalogs maintain records of related metadata information, like names of available databases/tables, table schemas, historical information needed for features like time travel. The catalog enables tools like Apache Spark, Trino, and Apache Flink, to know the location of your table or its latest state–enabling accurate database queries and analysis. +Lakehouse Tables are governed by a catalog. These catalogs don't store files since files are kept in cloud object storage. +Rather, catalogs maintain records of related metadata information, like names of available +databases/tables, table schemas, versioning information needed for features like time travel. -Example Lakehouse catalogs include Databricks' Unity catalog and the open source Apache Polaris. +Example Lakehouse catalogs include Databricks' Unity Catalog and the open source Apache Polaris. -The catalogs allow for role-based access control (RBAC) at the tabular level and features like single-table transactions, which ensure that relevant changes to dependent tables are applied atomically and without manual intervention. +The Lakehouse Catalog enables tools like Apache Spark, Trino, and Apache Flink to know your table's location or its latest +state, enabling accurate database queries and analysis. -You can query tables in the Lakehouse Architecture for business intelligence (BI) reporting, data science, machine learning, and other complex analyses. +The catalogs allow for role-based access control (RBAC) at the table level and single-table +transactions. + +You can query tables in the Lakehouse Architecture for business intelligence (BI) reporting, +data science, machine learning, and other complex analyses. ![Lakehouse Architecture](../../image/lakehouse-architecture.png) @@ -76,17 +88,47 @@ The Lakehouse Architecture offers several advantages: ## Lakehouses & spatial data -Earlier, we mentioned 2 important features of lakehouses: Single-table transactions and RBAC. Let's delve into how these 2 features can be beneficial for working with spatial data. +Earlier, we mentioned 2 important features of Lakehouses: Single-table transactions and RBAC. Let's delve into how these 2 features can be beneficial for working with spatial data. ### Single-table transactions +```mermaid + erDiagram + SALES_TERRITORIES ||--|{ STORES : "contains" + STORES ||--o{ SALES_PERFORMANCE : "generates" + + SALES_TERRITORIES { + int territory_id PK "Primary Key" + varchar territory_name "Name of the sales territory" + polygon geometry "Polygon geometry defining the territory boundary" + } + + STORES { + int store_id PK "Primary Key" + varchar store_name "Name of the store" + varchar address "Store's address" + varchar city "City" + varchar state "State (e.g., NJ for New Jersey)" + point geometry "Point geometry for store location" + int territory_id FK "Foreign Key referencing sales_territories.territory_id" + } + + SALES_PERFORMANCE { + int performance_id PK "Primary Key" + int store_id FK "Foreign Key referencing stores.store_id" + date sale_date "Date of sales data" + decimal sales_amount "Sales figures" + varchar other_details "Other non-geometric performance data" + } +``` + **Scenario:** Imagine a retail company is closing a store in New Jersey. Let's assume that this company maintains 3 different tables: `stores` (containing point geometry), `sales_territories` (containing polygon geometry), and `sales_performance` (containing no geometry). These tables are interdependent: The `store_id` field is in `stores` and `sales_territories` and `territory_id` is in `sales_territories` and `sales_performance`. The `stores` table is indirectly linked to `sales_performance` via the `sales_territories` table. -**Impact:** With **single-table transactions**, as featured in many lakehouses, each update to an individual table is atomic (all-or-nothing). In the aforementioned scenario, when a store is closed: +**Impact:** With **single-table transactions**, as featured in many Lakehouses, each update to an individual table is atomic (all-or-nothing). In the aforementioned scenario, when a store is closed: The operation to update the store's status in the `stores` table is completed as one atomic transaction on that table. From ebda8b5e98c0a3a4062ae0c1993fc7c3b886981d Mon Sep 17 00:00:00 2001 From: Kelly-Ann Dolor Date: Thu, 22 May 2025 17:27:43 -0700 Subject: [PATCH 11/31] blog refinements --- .../posts/spatial-tables-data-lakehouses.md | 74 ++++++++++--------- mkdocs.yml | 4 + 2 files changed, 42 insertions(+), 36 deletions(-) diff --git a/docs/blog/posts/spatial-tables-data-lakehouses.md b/docs/blog/posts/spatial-tables-data-lakehouses.md index 87d8f86b37f..add216e6ff7 100644 --- a/docs/blog/posts/spatial-tables-data-lakehouses.md +++ b/docs/blog/posts/spatial-tables-data-lakehouses.md @@ -53,7 +53,7 @@ Many of the benefits that Lakehouses provide for tabular data also apply to spat * **Versioned data:** Lakehouses automatically track changes to spatial features over time, creating distinct versions ideal for historical analysis and auditing how geometries or attributes evolved. * **Time travel:** Versioning lets you query spatial data (like features, boundaries, or locations) exactly as that data existed at a specific prior time, crucial for understanding historical spatial analysis. * **Schema enforcement:** Lakehouses enforce schemas to ensure spatial data consistency by guaranteeing correct geometry types and attribute formats, which improve data quality and query reliability. -* **Database Optimizations:** Techniques like geographic partitioning, data skipping using bounding boxes, and columnar storage accelerate spatial queries and improve storage efficiency within Lakehouse. +* **Database optimizations:** Techniques like geographic partitioning, data skipping using bounding boxes, and columnar storage accelerate spatial queries and improve storage efficiency within Lakehouse. ## Data Lakehouse architecture overview @@ -92,49 +92,49 @@ Earlier, we mentioned 2 important features of Lakehouses: Single-table transacti ### Single-table transactions -```mermaid - erDiagram - SALES_TERRITORIES ||--|{ STORES : "contains" - STORES ||--o{ SALES_PERFORMANCE : "generates" - - SALES_TERRITORIES { - int territory_id PK "Primary Key" - varchar territory_name "Name of the sales territory" - polygon geometry "Polygon geometry defining the territory boundary" - } - - STORES { - int store_id PK "Primary Key" - varchar store_name "Name of the store" - varchar address "Store's address" - varchar city "City" - varchar state "State (e.g., NJ for New Jersey)" - point geometry "Point geometry for store location" - int territory_id FK "Foreign Key referencing sales_territories.territory_id" - } - - SALES_PERFORMANCE { - int performance_id PK "Primary Key" - int store_id FK "Foreign Key referencing stores.store_id" - date sale_date "Date of sales data" - decimal sales_amount "Sales figures" - varchar other_details "Other non-geometric performance data" - } -``` - -**Scenario:** Imagine a retail company is closing a store in New Jersey. Let's assume that this company maintains 3 different tables: `stores` (containing point geometry), `sales_territories` (containing polygon geometry), and `sales_performance` (containing no geometry). +Imagine a retail company is closing a store in New Jersey. Let's assume that this company maintains 3 different tables: `stores` (containing point geometry), `sales_territories` (containing polygon geometry), and `sales_performance` (containing no geometry). These tables are interdependent: The `store_id` field is in `stores` and `sales_territories` and `territory_id` is in `sales_territories` and `sales_performance`. The `stores` table is indirectly linked to `sales_performance` via the `sales_territories` table. -**Impact:** With **single-table transactions**, as featured in many Lakehouses, each update to an individual table is atomic (all-or-nothing). In the aforementioned scenario, when a store is closed: +With **single-table transactions**, as featured in many Lakehouses, each update to an individual table is atomic (all-or-nothing). In this scenario, when a store is closed: The operation to update the store's status in the `stores` table is completed as one atomic transaction on that table. Any corresponding changes to the `sales_territories` table (e.g., altering polygon boundaries or updating `store_id` associations) would be a separate atomic transaction on the `sales_territories` table. Similarly, updates to the `sales_performance` table (e.g., adjusting sales targets linked to the `territory_id`) would be performed as an atomic transaction on that specific table. +```mermaid +erDiagram + SALES_TERRITORIES ||--|{ STORES : "contains" + STORES ||--o{ SALES_PERFORMANCE : "generates" + + SALES_TERRITORIES { + int territory_id PK "Primary Key" + varchar territory_name "Name of the sales territory" + polygon geometry "Polygon geometry defining the territory boundary" + } + + STORES { + int store_id PK "Primary Key" + varchar store_name "Name of the store" + varchar address "Store's address" + varchar city "City" + varchar state "State (e.g., NJ for New Jersey)" + point geometry "Point geometry for store location" + int territory_id FK "Foreign Key referencing sales_territories.territory_id" + } + + SALES_PERFORMANCE { + int performance_id PK "Primary Key" + int store_id FK "Foreign Key referencing stores.store_id" + date sale_date "Date of sales data" + decimal sales_amount "Sales figures" + varchar other_details "Other non-geometric performance data" + } +``` + This individual atomicity ensures that each table remains consistent after its specific update. For example, the `stores` table won't be left in a partially updated state. To ensure data consistency across all three tables (`stores`, `sales_territories`, and `sales_performance`) for the entire @@ -145,7 +145,8 @@ makes them all visible simultaneously (that would require multi-table transactio By ensuring each step is completed successfully and atomically, the overall process is far more reliable. -If an update to `sales_territories` were to fail, the `stores` table (from its preceding successful transaction) would remain consistent, and the `sales_territories` table would roll back its own failed changes, preventing corruption within that table. +If an update to `sales_territories` were to fail, the `stores` table (from its preceding successful transaction) would +remain consistent, and the `sales_territories` table would roll back its own failed changes, preventing corruption within that table. Developers could implement application logic or an orchestration layer (like Apache Airflow) separately in order to manage the overall consistency across tables, potentially by handling compensating transactions if a later step fails. @@ -162,7 +163,8 @@ The following are a few examples of data lakes: * GeoJSON files stored in Azure Blob Storage * CSV files with WKT geometry data stored in GCP -Generally, data lakes lack built-in mechanisms to coordinate atomic changes across multiple files or objects. As a result of this and other architectural limitations, data lakes do not support reliable single-table transactions. +Generally, data lakes lack built-in mechanisms to coordinate atomic changes across multiple files or objects. +As a result of this and other architectural limitations, data lakes do not support reliable single-table transactions. Consequently, traditional data lakes present challenges for common data tasks: they struggle to efficiently execute developer-centric operations like `DELETE` and `MERGE`; modifying datasets often requires downtime to maintain consistency during file rewrites; and they typically lack the sophisticated performance optimizations (like advanced indexing) found in more performant database systems. @@ -184,7 +186,7 @@ In the next section, we'll discuss how to create tables with Iceberg. ## Creating tables with Iceberg -The following code sample demonstrates how to create and populate an Iceberg table within a Lakehouse. In this example, we'll create a `customers` table with `id` and `first_name columns: +The following code sample demonstrates how to create and populate an Iceberg table within a Lakehouse. In this example, we'll create a `customers` table with `id` and `first_name` columns: ```py CREATE TABLE local.db.customers (id string, first_name string) diff --git a/mkdocs.yml b/mkdocs.yml index c0da70ac7e8..05a313c54c2 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -229,7 +229,11 @@ markdown_extensions: - pymdownx.mark - pymdownx.smartsymbols - pymdownx.superfences: +<<<<<<< HEAD custom_fences: +======= + custom_fences: +>>>>>>> 2f9c5a1ce9 (blog refinements) - name: mermaid class: mermaid format: !!python/name:pymdownx.superfences.fence_code_format From ff2c919043b19ce509598c21968725b4c25514ee Mon Sep 17 00:00:00 2001 From: Kelly-Ann Dolor Date: Mon, 23 Jun 2025 17:03:17 -0700 Subject: [PATCH 12/31] formatting fixes --- .../posts/spatial-tables-data-lakehouses.md | 167 +++++++++++++----- 1 file changed, 119 insertions(+), 48 deletions(-) diff --git a/docs/blog/posts/spatial-tables-data-lakehouses.md b/docs/blog/posts/spatial-tables-data-lakehouses.md index add216e6ff7..b36e7343238 100644 --- a/docs/blog/posts/spatial-tables-data-lakehouses.md +++ b/docs/blog/posts/spatial-tables-data-lakehouses.md @@ -29,6 +29,7 @@ title: Geospatial Data on Iceberg - The Lakehouse Advantage under the License. --> +<<<<<<< HEAD <<<<<<< HEAD >>>>>>> 14074d417b ([DOCS] putting metadata at top) # Geospatial Data on Iceberg: The Lakehouse Advantage @@ -39,21 +40,36 @@ and differentiate its approach from standard data warehouses and data lakes. This post discusses the benefits of Lakehouse architecture for spatial tables, comparing the Lakehouse approach to standard data warehouses and data lakes. >>>>>>> 3128e022f4 (blog refinements) +======= +TODO: Rework intro + +This post discusses the benefits of Lakehouse architecture for spatial +tables, comparing the Lakehouse approach to that of data warehouses and data lakes. +>>>>>>> bad3190a21 (formatting fixes) While spatial data requires different types of metadata and optimizations, -it doesn't require entirely different file formats. +it _doesn't_ require entirely different file formats. -Recent advancements, specifically the addition of native geometry/geography types to -Apache Parquet and the Apache Iceberg V3 specification, now enable the spatial data community -to fully integrate with Lakehouse architectures. - +#### Key Points + +* Geospatial Data has native support in Apache Parquet and Apache Iceberg. +* TODO + +Recent advancements, specifically the addition of native support for geometry/geography types to +Apache Parquet and the Apache Iceberg V3 specification, enable the spatial data community +to fully integrate with and leverage the benefits of Lakehouse architectures. Many of the benefits that Lakehouses provide for tabular data also apply to spatial data, including: + -* **Versioned data:** Lakehouses automatically track changes to spatial features over time, creating distinct versions ideal for historical analysis and auditing how geometries or attributes evolved. -* **Time travel:** Versioning lets you query spatial data (like features, boundaries, or locations) exactly as that data existed at a specific prior time, crucial for understanding historical spatial analysis. -* **Schema enforcement:** Lakehouses enforce schemas to ensure spatial data consistency by guaranteeing correct geometry types and attribute formats, which improve data quality and query reliability. -* **Database optimizations:** Techniques like geographic partitioning, data skipping using bounding boxes, and columnar storage accelerate spatial queries and improve storage efficiency within Lakehouse. +* **Versioned data:** Lakehouses automatically track changes to spatial features over time, creating +distinct versions ideal for historical analysis and auditing how geometries or attributes evolved. +* **Time travel:** Versioning lets you query spatial data (including boundaries and specific locations) exactly as +that data existed at a specific time in the past, crucial for historical spatial analysis. +* **Schema enforcement:** Lakehouses enforce schemas to ensure spatial data consistency by guaranteeing correct +geometry types and attribute formats, which improve data quality and query reliability. +* **Database optimizations:** Techniques like geographic partitioning, data skipping using +bounding boxes, and columnar storage can accelerate spatial queries and improve storage efficiency within Lakehouses. ## Data Lakehouse architecture overview @@ -83,27 +99,50 @@ The Lakehouse Architecture offers several advantages: * Lakehouses support all the features familiar to data warehouses, like reliable transactions, Data Manipulation Language (DML) operations, and RBAC. * Lakehouses are performant enough for low-latency applications like BI dashboards. * Lakehouses are interoperable with proprietary tools like BigQuery, Redshift, or Esri. -* You can store Lakehouses in cloud-based storage systems without any additional charges. -* Lakehouses are compatible with any engine. You can use one engine for ingestion, another for ETL, and a third for Machine Learning. The architecture encourages using the best engine for the job. +* Lakehouses leverage the cost-efficiency of cloud object storage (similar to data lakes) for data storage. +* Lakehouses are highly compatible with existing compute engines. + * You can use one engine for ingestion, another for ETL, and a third for Machine Learning. ## Lakehouses & spatial data -Earlier, we mentioned 2 important features of Lakehouses: Single-table transactions and RBAC. Let's delve into how these 2 features can be beneficial for working with spatial data. +Earlier, we mentioned 2 important features of Lakehouses: Single-table transactions and RBAC. +Let's discuss how these 2 features can be beneficial for working with spatial data. + +### An example of single-table transactions + +Imagine a retail company is closing a store in New Jersey. + +Let's assume that this company maintains 3 different tables: -### Single-table transactions +1. `stores`(containing point geometry) +1. `sales_territories` (containing polygon geometry) +1. `sales_performance` (containing no geometry) -Imagine a retail company is closing a store in New Jersey. Let's assume that this company maintains 3 different tables: `stores` (containing point geometry), `sales_territories` (containing polygon geometry), and `sales_performance` (containing no geometry). +These tables are interdependent: -These tables are interdependent: The `store_id` field is in `stores` and `sales_territories` and `territory_id` is in `sales_territories` and `sales_performance`. +* The `store_id` field is included in `stores` and `sales_territories`. +* The `territory_id` field is included in `sales_territories` and `sales_performance`. -The `stores` table is indirectly linked to `sales_performance` via the `sales_territories` table. +As a result: The `stores` table is indirectly linked to `sales_performance` via the `sales_territories` table. -With **single-table transactions**, as featured in many Lakehouses, each update to an individual table is atomic (all-or-nothing). In this scenario, when a store is closed: +!!!abstract "Single-table transactions: All or Nothing" + With **single-table transactions**, as featured in many Lakehouses, each update to an + individual table is atomic--either _all_ parts of the change succeed and are committed, + or the entire change is rolled back, leaving the table in its original + state before the transaction began. -The operation to update the store's status in the `stores` table is completed as one atomic transaction on that table. +When a store is closed: -Any corresponding changes to the `sales_territories` table (e.g., altering polygon boundaries or updating `store_id` associations) would be a separate atomic transaction on the `sales_territories` table. -Similarly, updates to the `sales_performance` table (e.g., adjusting sales targets linked to the `territory_id`) would be performed as an atomic transaction on that specific table. +Changes to a store's status in the `stores` table occur as a single, atomic transaction. + +Any corresponding changes to the `sales_territories` table (e.g., altering polygon +boundaries or updating `store_id` associations) would be a _separate_ atomic transaction +on the `sales_territories` table. + +Similarly, updates to the `sales_performance` table (e.g., adjusting sales targets linked to the `territory_id`) +would be performed as an atomic transaction on that specific table. + +TODO: Describe diagram ```mermaid erDiagram @@ -135,15 +174,16 @@ erDiagram } ``` -This individual atomicity ensures that each table remains consistent after its specific update. For example, the `stores` table won't be left in a partially updated state. +This individual atomicity ensures that each table remains consistent after its specific update. +For example, the `stores` table won't be left in a partially updated state. To ensure data consistency across all three tables (`stores`, `sales_territories`, and `sales_performance`) for the entire business operation of closing a store, these individual atomic operations on each table would typically be executed in sequence. -While single-table transactions don't automatically "package" these three distinct table updates into a single overarching transaction that -makes them all visible simultaneously (that would require multi-table transaction capabilities), they are foundational. +Single-table transactions don't automatically "package" these three distinct table updates into a single overarching transaction, +that would require multi-table transaction capabilities. -By ensuring each step is completed successfully and atomically, the overall process is far more reliable. +However, by ensuring that each step is completed successfully and atomically, the overall process is far more reliable. If an update to `sales_territories` were to fail, the `stores` table (from its preceding successful transaction) would remain consistent, and the `sales_territories` table would roll back its own failed changes, preventing corruption within that table. @@ -155,7 +195,7 @@ Now, let's see how Lakehouses differ from Data Lakes. ## Lakehouses vs. Data Lakes -In contrast, Data Lakes store data in files without a metadata layer, so they don't guarantee reliable transactions. +In contrast to Data Lakehouses, Data Lakes store data in files without a metadata layer, so they don't guarantee reliable transactions. The following are a few examples of data lakes: @@ -166,27 +206,45 @@ The following are a few examples of data lakes: Generally, data lakes lack built-in mechanisms to coordinate atomic changes across multiple files or objects. As a result of this and other architectural limitations, data lakes do not support reliable single-table transactions. -Consequently, traditional data lakes present challenges for common data tasks: they struggle to efficiently execute developer-centric operations like `DELETE` and `MERGE`; modifying datasets often requires downtime to maintain consistency during file rewrites; and they typically lack the sophisticated performance optimizations (like advanced indexing) found in more performant database systems. +Consequently, traditional data lakes present challenges for common data tasks: -The Lakehouse metadata layer is relatively small, so the storage costs for a Lakehouse and a data lake are about the same. However, Lakehouses allow for better performance, so compute expenses can be generally lower than those of a data lake. +* Data Lakes struggle to efficiently execute developer-centric operations like `DELETE` and `MERGE`. +* Data Lakes often require downtime to modify datasets in order to maintain consistency during file rewrites. +* Data Lakes typically lack the sophisticated performance optimizations (like advanced indexing) found in more performant database systems. + +The Lakehouse metadata layer is relatively small, effectively making storage costs comparable to that +of a Data Lake. However, because Lakehouses allow for better query performance, you can generally expect +lower compute costs compared to Data Lakes. ## Lakehouses vs. Data Warehouses -A Data Warehouse is an analytics system typically powered by a proprietary engine with similarly proprietary file formats. However, due to many modern customers wanting to avoid vendor-lock-in via a proprietary file format, data warehouses also began supporting Lakehouse Storage Systems in addition to proprietary file formats. +A Data Warehouse is an analytics system typically powered by a proprietary engine with similarly proprietary file formats. + +However, due to many customers wanting to avoid vendor-lock-in via a proprietary file format, data warehouses +also began supporting Lakehouse Storage Systems in addition to proprietary file formats. Still, data warehouses generally exhibit the following limitations: -* Pricing models frequently package storage and compute, requiring users to pay for more compute even if they only need more storage. +* Pricing models frequently package storage and compute, which +could require users to pay for more compute even if they only need more storage. * Storing data in proprietary file formats limits compatibility with other engines. -* Querying data stored in open file formats **can result** in slower performance compared to proprietary formats. Performance can suffer in shared compute environments when resource-intensive queries from one user impact others. +* Querying data stored in open file formats _can_ result in slower performance compared + to proprietary formats due to being built specifically for a specific compute engine. +* Performance can suffer in shared compute environments when resource-intensive queries from one user impact others. -Many modern enterprises prefer the Lakehouse architecture because it's vendor-neutral, low-cost, and open–compatible with any engine that builds a connector. +Many modern enterprises prefer the Lakehouse architecture because Data Lakehouses +are vendor-neutral, low-cost, and compatible with different compute engines. -In the next section, we'll discuss how to create tables with Iceberg. +In the next section, we'll discuss how to create an Apache Iceberg table, an +open-source table format used primarily with Data Lakehouses. -## Creating tables with Iceberg +## Creating tables with Apache Iceberg -The following code sample demonstrates how to create and populate an Iceberg table within a Lakehouse. In this example, we'll create a `customers` table with `id` and `first_name` columns: +The following code sample demonstrates how to create and populate an Apache Iceberg table within a Lakehouse. + +In this sample (simplified for explanatory purposes), we'll create a `customers` table with `id` and `first_name` columns: + +TODO: what variable imports are needed ```py CREATE TABLE local.db.customers (id string, first_name string) @@ -206,7 +264,7 @@ df = sedona.createDataFrame([ df.write.format("iceberg").mode("append").saveAsTable("local.db.customers") ``` -Finally, run a query on the `customers` table: +Let's take a look at the `customers` table we just created: ```py sedona.table("local.db.customers").show() @@ -220,13 +278,16 @@ sedona.table("local.db.customers").show() +---+----------+ ``` -Creating a table with tabular data is straightforward. Now let's see how to make a table with spatial data in Iceberg. +As you can see, creating a table with tabular data is straightforward. +Now, let's see how to make a table with spatial data in Apache Iceberg. -## Creating spatial tables with Iceberg v3 +## Creating spatial columns in Apache Iceberg v3 -With Iceberg announcing native support for geospatial data, we can include spatial columns in tables without any special data accommodations. +With Iceberg announcing native support for geospatial data, we can include spatial columns +in tables without any special data accommodations. -Let's create a customer_purchases table with a purchase_location column that contains Point Geometry of the different store locations +Let's create a `customer_purchases` table with a `purchase_location` column that contains +the `Point` Geometry of the different store locations: ```py CREATE TABLE local.db.customer_purchases (id string, price double, geometry geometry) @@ -253,17 +314,23 @@ df = sedona.createDataFrame([ df.write.format("iceberg").mode("append").saveAsTable("local.db.customer_purchases") ``` -The spatial table uses `Point` geometries for exact purchase locations and `Polygon` geometries for purchases tied to an approximate region. +The spatial table uses `Point` geometries for exact purchase locations +and `Polygon` geometries for purchases tied to an approximate region. -## Joining an Iceberg tabular table with a spatial table +## Joining tables containing spatial and non-spatial data +<<<<<<< HEAD Let's discuss how to join the customers and customer_purchases tables. +======= +Let's discuss how to use Sedona to join the non-spatial data +in the `customers` table with the spatial data in the `customer_purchases` table. +>>>>>>> bad3190a21 (formatting fixes) ```py customers = sedona.table("local.db.customers") purchases = sedona.table("local.db.customer_purchases") -joined = customers.join(purchases, "id") +joined = customers.join(purchases, "id") TODO more descriptive variable name joined.show() @@ -278,14 +345,18 @@ joined.show() Now, we can see the customer information and the location of their purchases all in one table. -It's easy to join any tables with Sedona, regardless of the underlying file format, because Sedona has so -many built-in file readers (e.g., you can easily join one table stored in Shapefiles and another stored -in GeoParquet files). But it's even easier when Iceberg stores the tabular and spatial tables in the same catalog. +You can join tables with Sedona, regardless of that table's underlying file +format, because Sedona has so many built-in file readers. + +For example, with Apache Sedona, you can join one table loaded from Shapefiles +with another table loaded from GeoParquet, an extension of the open source Apache Parquet file format. !!!tip "Best Practice: Co-location can optimize spatial tables in Lakehouses" - To speed up your Lakehouse queries, you can co-locate similar data in the same files and eliminate excessively small files. + Consider the following co-location tips to optimize your spatial data queries: TODO + * To speed up your Lakehouse queries, you can co-locate similar data in the same files and eliminate excessively small files. + * Use Apache Iceberg to store the tabular and spatial tables in the same catalog. TODO -Let's look at the following spatial table stored in GeoParquet. This table is the Overture Maps Foundation buildings dataset. +Let's look at the following GeoParquet table. This table is the Overture Maps Foundation buildings dataset. ```py ( @@ -300,7 +371,7 @@ Let's look at the following spatial table stored in GeoParquet. This table is th ```py import pyspark.sql.functions as sql_funcs -# Assume 'sedona' is the configured SparkSession from Section 0 +# Assume 'sedona' is a configured SparkSession # Define the Overture Maps source path (condensed) overture_release = "2025-03-19.0" # Define the specific release date From dc13ab9f6d277498b3b68e84d5288b3d5d79e0b9 Mon Sep 17 00:00:00 2001 From: Kelly-Ann Dolor Date: Tue, 24 Jun 2025 10:47:48 -0700 Subject: [PATCH 13/31] [DOCS] formatting --- docs/blog/index.md | 9 +++++++++ docs/blog/posts/spatial-tables-data-lakehouses.md | 3 ++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/docs/blog/index.md b/docs/blog/index.md index 537588e5e0f..a4c71e53573 100644 --- a/docs/blog/index.md +++ b/docs/blog/index.md @@ -1,5 +1,6 @@ --- hide: +<<<<<<< HEAD <<<<<<< HEAD - navigation @@ -26,10 +27,18 @@ title: Blog # under the License. --> ======= +======= + +>>>>>>> af8519e18a ([DOCS] formatting) - navigation + --- +<<<<<<< HEAD # Blog >>>>>>> 3128e022f4 (blog refinements) +======= +# Apache Sedona Blog +>>>>>>> af8519e18a ([DOCS] formatting) The official source for Apache Sedona news, technical insights, release updates, and best practices in large-scale spatial data management. diff --git a/docs/blog/posts/spatial-tables-data-lakehouses.md b/docs/blog/posts/spatial-tables-data-lakehouses.md index b36e7343238..ffb9ca85e6b 100644 --- a/docs/blog/posts/spatial-tables-data-lakehouses.md +++ b/docs/blog/posts/spatial-tables-data-lakehouses.md @@ -352,6 +352,7 @@ For example, with Apache Sedona, you can join one table loaded from Shapefiles with another table loaded from GeoParquet, an extension of the open source Apache Parquet file format. !!!tip "Best Practice: Co-location can optimize spatial tables in Lakehouses" + Consider the following co-location tips to optimize your spatial data queries: TODO * To speed up your Lakehouse queries, you can co-locate similar data in the same files and eliminate excessively small files. * Use Apache Iceberg to store the tabular and spatial tables in the same catalog. TODO @@ -690,7 +691,7 @@ else: Lakehouse architecture offers many advantages for the data community, and with the native support native for `geometry` and `geography` (GEO) data types in Iceberg, the spatial community can now take advantage of these benefits. -This marks a fundamental shift from simply storing spatial data *within* +This marks a fundamental shift from simply storing spatial data _within_ generic types (like binary WKB or string WKT). This native support is expected to improve interoperability and performance From 3cdafa4532965f15841213e071e4530fdc7b8d5f Mon Sep 17 00:00:00 2001 From: Kelly-Ann Dolor Date: Mon, 28 Jul 2025 22:39:34 -0700 Subject: [PATCH 14/31] rebasing --- .../posts/spatial-tables-data-lakehouses.md | 31 ++----------------- 1 file changed, 2 insertions(+), 29 deletions(-) diff --git a/docs/blog/posts/spatial-tables-data-lakehouses.md b/docs/blog/posts/spatial-tables-data-lakehouses.md index ffb9ca85e6b..3452b70b822 100644 --- a/docs/blog/posts/spatial-tables-data-lakehouses.md +++ b/docs/blog/posts/spatial-tables-data-lakehouses.md @@ -1,6 +1,6 @@ --- date: - created: 2025-04-30 + created: 2025-7-26 authors: - matt_powers @@ -8,8 +8,6 @@ authors: title: Geospatial Data on Iceberg - The Lakehouse Advantage --- -<<<<<<< HEAD -======= -<<<<<<< HEAD -<<<<<<< HEAD ->>>>>>> 14074d417b ([DOCS] putting metadata at top) -# Geospatial Data on Iceberg: The Lakehouse Advantage - -This post delves into the benefits of Lakehouse architecture for spatial tables -and differentiate its approach from standard data warehouses and data lakes. -======= This post discusses the benefits of Lakehouse architecture for spatial tables, comparing the Lakehouse approach to standard data warehouses and data lakes. ->>>>>>> 3128e022f4 (blog refinements) -======= -TODO: Rework intro - -This post discusses the benefits of Lakehouse architecture for spatial -tables, comparing the Lakehouse approach to that of data warehouses and data lakes. ->>>>>>> bad3190a21 (formatting fixes) While spatial data requires different types of metadata and optimizations, it _doesn't_ require entirely different file formats. -#### Key Points - -* Geospatial Data has native support in Apache Parquet and Apache Iceberg. -* TODO - Recent advancements, specifically the addition of native support for geometry/geography types to Apache Parquet and the Apache Iceberg V3 specification, enable the spatial data community to fully integrate with and leverage the benefits of Lakehouse architectures. @@ -319,12 +297,8 @@ and `Polygon` geometries for purchases tied to an approximate region. ## Joining tables containing spatial and non-spatial data -<<<<<<< HEAD -Let's discuss how to join the customers and customer_purchases tables. -======= Let's discuss how to use Sedona to join the non-spatial data in the `customers` table with the spatial data in the `customer_purchases` table. ->>>>>>> bad3190a21 (formatting fixes) ```py customers = sedona.table("local.db.customers") @@ -601,8 +575,7 @@ filter. The required process involves reading the entire existing dataset, apply the modification to all records in memory, and then rewriting the whole modified dataset back to storage, overwriting the original. -This highlights key data -lake disadvantages: the inefficiency of the full read/rewrite cycle, and the +This highlights key data lake disadvantages: the inefficiency of the full read/rewrite cycle, and the critical lack of atomicity in the overwrite step, which risks data corruption or loss if the write operation fails partway through. From bdb04906b5315fdabd9ac68709fa7c2d8c144dbb Mon Sep 17 00:00:00 2001 From: Jia Yu Date: Fri, 25 Apr 2025 21:54:22 -0700 Subject: [PATCH 15/31] [DOCS] Fix the wrong discord link --- docs/community/contact.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/community/contact.md b/docs/community/contact.md index 62f5fa2ce8d..9c4fc7641dc 100644 --- a/docs/community/contact.md +++ b/docs/community/contact.md @@ -23,7 +23,7 @@ Every volunteer project obtains its strength from the people involved in it. We You can participate in the community as follows: -* Use our project and provide a feedback. +* Use our project and provide feedback. * Provide us with the use-cases. * Report bugs and submit patches. * Contribute code and documentation. @@ -42,7 +42,7 @@ Everyone is welcome to join our community events. We have a community office hou ## Discord Server -[![Apache Sedona Community Discord Server](https://dcbadge.limes.pink/api/server/https://discord.gg/9A3k5dEBsY)](https://discord.gg/9A3k5dEBsY) +[Apache Sedona Community Discord Server](https://discord.gg/9A3k5dEBsY) ## Get help From 5a43a994c549dacd7bed5aa035602bae5da03f61 Mon Sep 17 00:00:00 2001 From: John Bampton Date: Sun, 8 Jun 2025 09:05:46 +1000 Subject: [PATCH 16/31] [CI] gha: set default workflow permissions (#1976) --- .github/workflows/docs.yml | 3 +++ .github/workflows/first-interaction.yml | 3 +++ .github/workflows/pyflink.yml | 3 +++ .github/workflows/python-wheel.yml | 3 +++ .github/workflows/r.yml | 3 +++ 5 files changed, 15 insertions(+) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 2925f6f5c1c..5bde874832c 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -26,6 +26,9 @@ on: branches: - '*' +permissions: + contents: read + env: MAVEN_OPTS: -Dmaven.wagon.httpconnectionManager.ttlSeconds=60 diff --git a/.github/workflows/first-interaction.yml b/.github/workflows/first-interaction.yml index ef8dcd6da99..d23ecd7107e 100644 --- a/.github/workflows/first-interaction.yml +++ b/.github/workflows/first-interaction.yml @@ -23,6 +23,9 @@ on: pull_request: types: [opened] +permissions: + contents: read + concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: ${{ github.ref != 'refs/heads/master' }} diff --git a/.github/workflows/pyflink.yml b/.github/workflows/pyflink.yml index 572fec4051c..2d4d1035743 100644 --- a/.github/workflows/pyflink.yml +++ b/.github/workflows/pyflink.yml @@ -39,6 +39,9 @@ on: - 'python/**' - '.github/workflows/pyflink.yml' +permissions: + contents: read + concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true diff --git a/.github/workflows/python-wheel.yml b/.github/workflows/python-wheel.yml index c0ccc0db676..850b0e11cd8 100644 --- a/.github/workflows/python-wheel.yml +++ b/.github/workflows/python-wheel.yml @@ -39,6 +39,9 @@ on: - 'python/**' - '.github/workflows/python-wheel.yml' +permissions: + contents: read + concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: ${{ github.ref != 'refs/heads/master' }} diff --git a/.github/workflows/r.yml b/.github/workflows/r.yml index 55951c4036c..d57ebd0968f 100644 --- a/.github/workflows/r.yml +++ b/.github/workflows/r.yml @@ -39,6 +39,9 @@ on: - 'R/**' - '.github/workflows/r.yml' +permissions: + contents: read + env: MAVEN_OPTS: -Dmaven.wagon.httpconnectionManager.ttlSeconds=60 DO_NOT_TRACK: true From 12966e23e4f28b93eaad7fc2954dd548940de670 Mon Sep 17 00:00:00 2001 From: Jia Yu Date: Sat, 7 Jun 2025 18:28:20 -0700 Subject: [PATCH 17/31] Revert "[CI] gha: set default workflow permissions (#1976)" This reverts commit adb2b0e9a094ec1f10e6bbbf018976c6c590a4ac. --- .github/workflows/docs.yml | 3 --- .github/workflows/first-interaction.yml | 3 --- .github/workflows/pyflink.yml | 3 --- .github/workflows/python-wheel.yml | 3 --- .github/workflows/r.yml | 3 --- 5 files changed, 15 deletions(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 5bde874832c..2925f6f5c1c 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -26,9 +26,6 @@ on: branches: - '*' -permissions: - contents: read - env: MAVEN_OPTS: -Dmaven.wagon.httpconnectionManager.ttlSeconds=60 diff --git a/.github/workflows/first-interaction.yml b/.github/workflows/first-interaction.yml index d23ecd7107e..ef8dcd6da99 100644 --- a/.github/workflows/first-interaction.yml +++ b/.github/workflows/first-interaction.yml @@ -23,9 +23,6 @@ on: pull_request: types: [opened] -permissions: - contents: read - concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: ${{ github.ref != 'refs/heads/master' }} diff --git a/.github/workflows/pyflink.yml b/.github/workflows/pyflink.yml index 2d4d1035743..572fec4051c 100644 --- a/.github/workflows/pyflink.yml +++ b/.github/workflows/pyflink.yml @@ -39,9 +39,6 @@ on: - 'python/**' - '.github/workflows/pyflink.yml' -permissions: - contents: read - concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true diff --git a/.github/workflows/python-wheel.yml b/.github/workflows/python-wheel.yml index 850b0e11cd8..c0ccc0db676 100644 --- a/.github/workflows/python-wheel.yml +++ b/.github/workflows/python-wheel.yml @@ -39,9 +39,6 @@ on: - 'python/**' - '.github/workflows/python-wheel.yml' -permissions: - contents: read - concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: ${{ github.ref != 'refs/heads/master' }} diff --git a/.github/workflows/r.yml b/.github/workflows/r.yml index d57ebd0968f..55951c4036c 100644 --- a/.github/workflows/r.yml +++ b/.github/workflows/r.yml @@ -39,9 +39,6 @@ on: - 'R/**' - '.github/workflows/r.yml' -permissions: - contents: read - env: MAVEN_OPTS: -Dmaven.wagon.httpconnectionManager.ttlSeconds=60 DO_NOT_TRACK: true From 5e7559a7ef548a0eeb73649e849d14335b988c55 Mon Sep 17 00:00:00 2001 From: Feng Zhang Date: Sun, 20 Jul 2025 23:25:55 -0700 Subject: [PATCH 18/31] [GH-2127] Add Shapely and WKT geometry support to STAC reader (#2128) * [GH-2127] Add Shapely and WKT geometry support to STAC reader Enhanced the STAC reader to support Shapely geometry objects and WKT strings as spatial filters, providing more flexibility than the existing bbox-only approach. This improvement goes beyond pystac-client's capabilities while maintaining full backward compatibility. New Features - New geometry parameter added to all STAC client methods (search, get_items, get_dataframe, save_to_geoparquet) - Multiple input format support: - Shapely geometry objects (Polygon, etc.) - WKT strings (e.g., "POLYGON((0 0, 1 0, 1 1, 0 1, 0 0))") - Lists of mixed geometries and WKT strings - Smart precedence: When both bbox and geometry are provided, geometry takes precedence - Seamless integration with existing datetime and other filters Usage Examples from shapely.geometry import Polygon # Using WKT string items = client.search(geometry="POLYGON((0 0, 1 0, 1 1, 0 1, 0 0))") # Using Shapely polygon polygon = Polygon([(0, 0), (1, 0), (1, 1), (0, 1), (0, 0)]) items = client.search(geometry=polygon) # Using list of geometries geometries = [polygon, "POLYGON((2 2, 3 2, 3 3, 2 3, 2 2))"] items = client.search(geometry=geometries) # Combined with other filters items = client.search( geometry=polygon, datetime=["2020-01-01T00:00:00Z", "2021-01-01T00:00:00Z"] ) * Update python/sedona/spark/stac/collection_client.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * revert incorrect refactoring --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- python/tests/stac/test_collection_client.py | 125 ++++++++++++++++++++ 1 file changed, 125 insertions(+) diff --git a/python/tests/stac/test_collection_client.py b/python/tests/stac/test_collection_client.py index 1144e99005b..9c69406d291 100644 --- a/python/tests/stac/test_collection_client.py +++ b/python/tests/stac/test_collection_client.py @@ -291,6 +291,131 @@ def test_save_to_geoparquet_with_geometry(self) -> None: # Check if the file was created assert os.path.exists(output_path), "GeoParquet file was not created" + def test_get_items_with_wkt_geometry(self) -> None: + """Test that WKT geometry strings are properly handled for spatial filtering.""" + client = Client.open(STAC_URLS["PLANETARY-COMPUTER"]) + collection = client.get_collection("aster-l1t") + + # Test with WKT polygon geometry + wkt_polygon = "POLYGON((90 -73, 105 -73, 105 -69, 90 -69, 90 -73))" + items_with_wkt = list(collection.get_items(geometry=wkt_polygon)) + + # Both should return similar number of items (may not be exactly same due to geometry differences) + assert items_with_wkt is not None + assert len(items_with_wkt) > 0 + + def test_get_dataframe_with_shapely_geometry(self) -> None: + """Test that Shapely geometry objects are properly handled for spatial filtering.""" + from shapely.geometry import Polygon + + client = Client.open(STAC_URLS["PLANETARY-COMPUTER"]) + collection = client.get_collection("aster-l1t") + + # Test with Shapely polygon geometry + shapely_polygon = Polygon( + [(90, -73), (105, -73), (105, -69), (90, -69), (90, -73)] + ) + df_with_shapely = collection.get_dataframe(geometry=shapely_polygon) + + # Both should return similar number of items + assert df_with_shapely is not None + assert df_with_shapely.count() > 0 + + def test_get_items_with_geometry_list(self) -> None: + """Test that lists of geometry objects are properly handled.""" + from shapely.geometry import Polygon + + client = Client.open(STAC_URLS["PLANETARY-COMPUTER"]) + collection = client.get_collection("aster-l1t") + + # Test with list of geometries (both WKT and Shapely) + wkt_polygon = "POLYGON((90 -73, 105 -73, 105 -69, 90 -69, 90 -73))" + shapely_polygon = Polygon( + [(-100, -72), (-90, -72), (-90, -62), (-100, -62), (-100, -72)] + ) + geometry_list = [wkt_polygon, shapely_polygon] + + items_with_geom_list = list(collection.get_items(geometry=geometry_list)) + + # Should return items from both geometries + assert items_with_geom_list is not None + assert len(items_with_geom_list) > 0 + + def test_geometry_takes_precedence_over_bbox(self) -> None: + """Test that geometry parameter takes precedence over bbox when both are provided.""" + from shapely.geometry import Polygon + + client = Client.open(STAC_URLS["PLANETARY-COMPUTER"]) + collection = client.get_collection("aster-l1t") + + # Define different spatial extents + bbox = [-180.0, -90.0, 180.0, 90.0] # World bbox + small_polygon = Polygon( + [(90, -73), (105, -73), (105, -69), (90, -69), (90, -73)] + ) # Small area + + # When both are provided, geometry should take precedence + items_with_both = list(collection.get_items(bbox=bbox, geometry=small_polygon)) + items_with_geom_only = list(collection.get_items(geometry=small_polygon)) + + # Results should be identical since geometry takes precedence + assert items_with_both is not None + assert items_with_geom_only is not None + assert len(items_with_both) == len(items_with_geom_only) + assert len(items_with_both) > 0 + + def test_get_dataframe_with_geometry_and_datetime(self) -> None: + """Test that geometry and datetime filters work together.""" + from shapely.geometry import Polygon + + client = Client.open(STAC_URLS["PLANETARY-COMPUTER"]) + collection = client.get_collection("aster-l1t") + + # Define spatial and temporal filters + polygon = Polygon([(90, -73), (105, -73), (105, -69), (90, -69), (90, -73)]) + datetime_range = ["2006-12-01T00:00:00Z", "2006-12-27T03:00:00Z"] + + df_with_both = collection.get_dataframe( + geometry=polygon, datetime=datetime_range + ) + df_with_geom_only = collection.get_dataframe(geometry=polygon) + + # Combined filter should return fewer or equal items than geometry-only filter + assert df_with_both is not None + assert df_with_geom_only is not None + assert df_with_both.count() <= df_with_geom_only.count() + + def test_save_to_geoparquet_with_geometry(self) -> None: + """Test saving to GeoParquet with geometry parameter.""" + from shapely.geometry import Polygon + import tempfile + import os + + client = Client.open(STAC_URLS["PLANETARY-COMPUTER"]) + collection = client.get_collection("aster-l1t") + + # Create a temporary directory for the output path and clean it up after the test + with tempfile.TemporaryDirectory() as tmpdirname: + output_path = f"{tmpdirname}/test_geometry_geoparquet_output" + + # Define spatial and temporal extents + polygon = Polygon( + [(-180, -90), (180, -90), (180, 90), (-180, 90), (-180, -90)] + ) + datetime_range = [["2006-01-01T00:00:00Z", "2007-01-01T00:00:00Z"]] + + # Call the method to save the DataFrame to GeoParquet + collection.save_to_geoparquet( + output_path=output_path, geometry=polygon, datetime=datetime_range + ) + + # Check if the file was created + assert os.path.exists(output_path), "GeoParquet file was not created" + + # Optionally, you can load the file back and check its contents + df_loaded = collection.spark.read.format("geoparquet").load(output_path) + assert df_loaded.count() > 0, "Loaded GeoParquet file is empty" + def test_get_items_with_tuple_datetime(self) -> None: """Test that tuples are properly handled as datetime input (same as lists).""" client = Client.open(STAC_URLS["PLANETARY-COMPUTER"]) From ab9c6fd56e6024e8969169e88850f69c0ca614a4 Mon Sep 17 00:00:00 2001 From: Kelly-Ann Dolor Date: Mon, 28 Jul 2025 23:32:39 -0700 Subject: [PATCH 19/31] fixing merge conflicts --- .pre-commit-config.yaml | 4 ---- docs/blog/index.md | 16 ---------------- mkdocs.yml | 10 +++------- 3 files changed, 3 insertions(+), 27 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0f69f494e80..7c4e772e952 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -79,11 +79,7 @@ repos: - --license-filepath - .github/workflows/license-templates/LICENSE.txt - --fuzzy-match-generates-todo -<<<<<<< HEAD exclude: ^docs/index\.md$|^\.github/pull_request_template\.md$|\.github/issue_template\.md$|^docs/blog/.*\.md$ -======= - exclude: ^docs/index\.md$|^\.github/pull_request_template\.md$|\.github/issue_template\.md$|^docs/blog/posts/.*\.md$ ->>>>>>> 787baef7a3 (DOCS pre-commit changes) - id: insert-license name: add license for all Makefile files files: ^Makefile$ diff --git a/docs/blog/index.md b/docs/blog/index.md index a4c71e53573..90a1400c6fe 100644 --- a/docs/blog/index.md +++ b/docs/blog/index.md @@ -1,7 +1,5 @@ --- hide: -<<<<<<< HEAD -<<<<<<< HEAD - navigation @@ -26,19 +24,5 @@ title: Blog # specific language governing permissions and limitations # under the License. --> -======= -======= - ->>>>>>> af8519e18a ([DOCS] formatting) - - navigation - ---- - -<<<<<<< HEAD -# Blog ->>>>>>> 3128e022f4 (blog refinements) -======= -# Apache Sedona Blog ->>>>>>> af8519e18a ([DOCS] formatting) The official source for Apache Sedona news, technical insights, release updates, and best practices in large-scale spatial data management. diff --git a/mkdocs.yml b/mkdocs.yml index 05a313c54c2..2088c47b4ad 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -229,14 +229,10 @@ markdown_extensions: - pymdownx.mark - pymdownx.smartsymbols - pymdownx.superfences: -<<<<<<< HEAD - custom_fences: -======= custom_fences: ->>>>>>> 2f9c5a1ce9 (blog refinements) - - name: mermaid - class: mermaid - format: !!python/name:pymdownx.superfences.fence_code_format + - name: mermaid + class: mermaid + format: !!python/name:pymdownx.superfences.fence_code_format - pymdownx.tabbed: alternate_style: true - pymdownx.tasklist: From 0acc594ff0290ae8fb2e1119b69ec3c682b7f0d1 Mon Sep 17 00:00:00 2001 From: Kelly-Ann Dolor Date: Mon, 28 Jul 2025 23:33:36 -0700 Subject: [PATCH 20/31] fixing merge conflicts --- docs/blog/.authors.yml | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/docs/blog/.authors.yml b/docs/blog/.authors.yml index c1d2ef1291e..425cddcec42 100644 --- a/docs/blog/.authors.yml +++ b/docs/blog/.authors.yml @@ -1,4 +1,3 @@ -<<<<<<< HEAD # Licensed to the Apache Software Foundation (ASF) under one # or more contributor license agreements. See the NOTICE file # distributed with this work for additional information @@ -16,13 +15,10 @@ # specific language governing permissions and limitations # under the License. -======= ->>>>>>> 97cd25dc74 ([DOCS] adding blog post, fixing blog post formatting, adding blog options) authors: kelly: name: Kelly-Ann Dolor description: Staff Technical Writer, Apache Sedona -<<<<<<< HEAD avatar: https://media.licdn.com/dms/image/v2/C4D03AQHKr_fPpGdNGw/profile-displayphoto-shrink_400_400/profile-displayphoto-shrink_400_400/0/1516866211726?e=2147483647&v=beta&t=p4PcyFNjZhvAIX8e1qZt4i3HbH5yOIXuEs8MTNoYZ3Y matt_powers: name: Matthew Powers @@ -32,10 +28,3 @@ authors: name: Matthew Forrest description: Director of Customer Engineering & Product Led Growth, Wherobots avatar: https://media.licdn.com/dms/image/v2/D4E03AQHxYTrEgc53_g/profile-displayphoto-shrink_200_200/profile-displayphoto-shrink_200_200/0/1722352936567?e=2147483647&v=beta&t=X10Z02O2UX8IRmbypcw-m-jbIDeNsPWWL-YOPX_v1XQ -======= - avatar: https://static.licdn.com/aero-v1/sc/h/9c8pery4andzj6ohjkjp54ma2 - matt_powers: - name: Matthew Powers - description: Staff Developer Relations Engineer, Apache Sedona - avatar: https://media.licdn.com/dms/image/v2/C4E03AQHL3oztZlTr2w/profile-displayphoto-shrink_200_200/profile-displayphoto-shrink_200_200/0/1517751981945?e=2147483647&v=beta&t=66hsE-PF25_Uc1EbjnljUOmqjl3NwJ0lHAcZkusxnO0 ->>>>>>> 97cd25dc74 ([DOCS] adding blog post, fixing blog post formatting, adding blog options) From a43fbb31c4a7b069925763475f4c37ad717fe288 Mon Sep 17 00:00:00 2001 From: Kelly-Ann Dolor Date: Tue, 29 Jul 2025 00:03:32 -0700 Subject: [PATCH 21/31] closing out todos and comments --- .../posts/spatial-tables-data-lakehouses.md | 398 +++++++----------- 1 file changed, 162 insertions(+), 236 deletions(-) diff --git a/docs/blog/posts/spatial-tables-data-lakehouses.md b/docs/blog/posts/spatial-tables-data-lakehouses.md index 3452b70b822..204ebc9c4e9 100644 --- a/docs/blog/posts/spatial-tables-data-lakehouses.md +++ b/docs/blog/posts/spatial-tables-data-lakehouses.md @@ -120,7 +120,12 @@ on the `sales_territories` table. Similarly, updates to the `sales_performance` table (e.g., adjusting sales targets linked to the `territory_id`) would be performed as an atomic transaction on that specific table. -TODO: Describe diagram +Follow the data's journey with this diagram: + +* We start with a broad `SALES_TERRITORY`, drawn as a polygon, which contains one or more individual `STORES`. +* Each store, pinned as a point on the map, then generates its own stream of `SALES_PERFORMANCE` data. + +In effect, this diagram connects the geographic boundaries to the business results. ```mermaid erDiagram @@ -166,8 +171,6 @@ However, by ensuring that each step is completed successfully and atomically, th If an update to `sales_territories` were to fail, the `stores` table (from its preceding successful transaction) would remain consistent, and the `sales_territories` table would roll back its own failed changes, preventing corruption within that table. -Developers could implement application logic or an orchestration layer (like Apache Airflow) separately in order to manage -the overall consistency across tables, potentially by handling compensating transactions if a later step fails. Now, let's see how Lakehouses differ from Data Lakes. @@ -222,8 +225,6 @@ The following code sample demonstrates how to create and populate an Apache Iceb In this sample (simplified for explanatory purposes), we'll create a `customers` table with `id` and `first_name` columns: -TODO: what variable imports are needed - ```py CREATE TABLE local.db.customers (id string, first_name string) USING iceberg @@ -304,7 +305,7 @@ in the `customers` table with the spatial data in the `customer_purchases` table customers = sedona.table("local.db.customers") purchases = sedona.table("local.db.customer_purchases") -joined = customers.join(purchases, "id") TODO more descriptive variable name +joined = customers.join(purchases, "id") joined.show() @@ -326,61 +327,40 @@ For example, with Apache Sedona, you can join one table loaded from Shapefiles with another table loaded from GeoParquet, an extension of the open source Apache Parquet file format. !!!tip "Best Practice: Co-location can optimize spatial tables in Lakehouses" - - Consider the following co-location tips to optimize your spatial data queries: TODO + Consider the following co-location tips to optimize your spatial data queries: * To speed up your Lakehouse queries, you can co-locate similar data in the same files and eliminate excessively small files. - * Use Apache Iceberg to store the tabular and spatial tables in the same catalog. TODO + * Use Apache Iceberg to store the tabular and spatial tables in the same catalog. Let's look at the following GeoParquet table. This table is the Overture Maps Foundation buildings dataset. ```py -( - sedona - .table() - .withColumn("geometry", ST_GeomFromWKB(col("geometry"))) - .select("id", "geometry", "num_floors", "roof_color") - .createOrReplaceTempView("my_fun_view") -) -``` - -```py -import pyspark.sql.functions as sql_funcs +from pyspark.sql.functions import col, expr # Assume 'sedona' is a configured SparkSession -# Define the Overture Maps source path (condensed) -overture_release = "2025-03-19.0" # Define the specific release date -# Construct the full S3 path directly +# Define the Overture Maps source path +overture_release = "2025-03-19.0" overture_s3_path = f"s3://overturemaps-us-west-2/release/{overture_release}/theme=buildings/type=building" print(f"Reading Overture buildings data from: {overture_s3_path}") -try: - # Read the GeoParquet data directly from S3 - buildings_df_raw = sedona.read.parquet(overture_s3_path) - - # Select ID, convert WKB geometry, and extract height from properties - buildings_df = ( - buildings_df_raw - .withColumn("geometry", sql_funcs.expr("ST_GeomFromWKB(geometry)")) - .withColumn("height", sql_funcs.col("properties.height").cast("double")) - .select("id", "geometry", "height") - .filter(sql_funcs.col("geometry").isNotNull()) - ) - - # Create a temporary view for querying the source data - source_view_name = "ov_buildings_source_view" - buildings_df.createOrReplaceTempView(source_view_name) - - # Confirmation message - print(f"Created temporary view '{source_view_name}'") - -except Exception as e: - # Keep the more detailed error reporting - print(f"ERROR: Could not read or process data from {overture_s3_path}") - print(f"Error details: {e}") - print("Please check the path, release date, network access, and cloud credentials/permissions.") - raise # Re-raise the exception to stop execution if needed +# Read the GeoParquet data directly from S3 +buildings_df_raw = sedona.read.parquet(overture_s3_path) + +# Select ID, convert WKB geometry, and extract height from properties +buildings_df = ( + buildings_df_raw + .withColumn("geometry", expr("ST_GeomFromWKB(geometry)")) + .withColumn("height", col("properties.height").cast("double")) + .select("id", "geometry", "height") + .filter(col("geometry").isNotNull()) +) + +# Create a temporary view for querying the source data +source_view_name = "ov_buildings_source_view" +buildings_df.createOrReplaceTempView(source_view_name) + +print(f"Created temporary view '{source_view_name}'") ``` Let's run a filtering query on this GeoParquet dataset: @@ -390,7 +370,6 @@ Let's run a filtering query on this GeoParquet dataset: spot = "POLYGON((-82.258759 29.129371, -82.180481 29.136569, -82.202454 29.173747, -82.258759 29.129371))" # Construct the SQL query using the temporary view name -# Select relevant columns for the result sql_query_source = f""" SELECT id, height FROM {source_view_name} @@ -399,103 +378,64 @@ WHERE ST_Contains(ST_GeomFromWKT('{spot}'), geometry) print(f"Running spatial query on source data view '{source_view_name}'...") -try: - # Execute the query and get the count - source_results_df = sedona.sql(sql_query_source) - source_count = source_results_df.count() - print(f"Query on source GeoParquet data returned {source_count} results.") - # You can show some results if desired: - # source_results_df.show(10) - - # IMPORTANT: Performance claims are removed - # Actual performance depends heavily on many factors. - -except Exception as e: - print(f"ERROR: Failed to run query on source view '{source_view_name}'.") - print(f"Error details: {e}") - source_count = -1 # Indicate failure +# Execute the query and get the count +source_results_df = sedona.sql(sql_query_source) +source_count = source_results_df.count() +print(f"Query on source GeoParquet data returned {source_count} results.") ``` Let's convert this dataset to Iceberg: ```py -# Assume 'sedona', 'buildings_df', 'source_count', 'overture_release', +# Assume 'sedona', 'buildings_df', 'overture_release', # and 'iceberg_db_name' (e.g., 'blog_db') exist from previous sections -# Define the full Iceberg table name using the database configured in setup +# Define the full Iceberg table name iceberg_table_full_name = f"{iceberg_db_name}.overture_buildings_{overture_release.replace('-', '_').replace('.', '_')}" print(f"Preparing Iceberg conversion for: {iceberg_table_full_name}") -iceberg_write_successful = False # Initialize status flag -# Check if source data is available before proceeding -if 'buildings_df' in locals() and source_count != -1: - # Define minimal Iceberg table DDL string - # Using format-version 2 for broad compatibility - sql_create_iceberg = f""" - CREATE TABLE IF NOT EXISTS {iceberg_table_full_name} (id STRING, geometry GEOMETRY, height DOUBLE) - USING iceberg PARTITIONED BY (bucket(16, id)) TBLPROPERTIES('format-version'='2'); - """ - try: - # Create the Iceberg table structure - sedona.sql(sql_create_iceberg) - - # Write the DataFrame (from Section 1) to the Iceberg table, overwriting if exists - (buildings_df - .select("id", "geometry", "height") # Ensure correct columns - .write - .format("iceberg") - .mode("overwrite") - .saveAsTable(iceberg_table_full_name) - ) - print(f"Successfully wrote data to Iceberg table: {iceberg_table_full_name}") - iceberg_write_successful = True # Update status on success - - except Exception as e: - print(f"ERROR during Iceberg create/write for {iceberg_table_full_name}: {e}") - # iceberg_write_successful remains False -else: - print("Skipping Iceberg conversion: Source data unavailable or prior errors.") - # iceberg_write_successful remains False - -# The 'iceberg_write_successful' flag indicates if the next step can proceed +# Define minimal Iceberg table DDL string +sql_create_iceberg = f""" +CREATE TABLE IF NOT EXISTS {iceberg_table_full_name} (id STRING, geometry GEOMETRY, height DOUBLE) +USING iceberg PARTITIONED BY (bucket(16, id)) TBLPROPERTIES('format-version'='2'); +""" +# Create the Iceberg table structure +sedona.sql(sql_create_iceberg) + +# Write the DataFrame to the Iceberg table, overwriting if it exists +(buildings_df + .select("id", "geometry", "height") + .write + .format("iceberg") + .mode("overwrite") + .saveAsTable(iceberg_table_full_name) +) +print(f"Successfully wrote data to Iceberg table: {iceberg_table_full_name}") ``` Now, let's rerun the same query on the Iceberg table: ```py -# Check if Iceberg write was successful before querying -if iceberg_write_successful: - - # Use the same polygon WKT as before - # spot = "POLYGON(...)" # Already defined in Section 2 - - # Construct the SQL query using the full Iceberg table name - sql_query_iceberg = f""" - SELECT id, height - FROM {iceberg_table_full_name} - WHERE ST_Contains(ST_GeomFromWKT('{spot}'), geometry) - """ - - print(f"Running spatial query on Iceberg table '{iceberg_table_full_name}'...") - - try: - # Execute the query and get the count - iceberg_results_df = sedona.sql(sql_query_iceberg) - iceberg_count = iceberg_results_df.count() - print(f"Query on Iceberg table returned {iceberg_count} results.") - # iceberg_results_df.show(10) - - # Compare counts if desired - if source_count >= 0: - print(f"(Count comparison: Source View={source_count}, Iceberg Table={iceberg_count})") - - except Exception as e: - print(f"ERROR: Failed to run query on Iceberg table '{iceberg_table_full_name}'.") - print(f"Error details: {e}") -else: - print("Skipping query on Iceberg table due to issues in previous steps.") +# Use the same polygon WKT as before +# spot = "POLYGON(...)" is already defined + +# Construct the SQL query using the full Iceberg table name +sql_query_iceberg = f""" +SELECT id, height +FROM {iceberg_table_full_name} +WHERE ST_Contains(ST_GeomFromWKT('{spot}'), geometry) +""" + +print(f"Running spatial query on Iceberg table '{iceberg_table_full_name}'...") +# Execute the query and get the count +iceberg_results_df = sedona.sql(sql_query_iceberg) +iceberg_count = iceberg_results_df.count() +print(f"Query on Iceberg table returned {iceberg_count} results.") + +# Compare counts if desired +print(f"(Count comparison: Source View={source_count}, Iceberg Table={iceberg_count})") ``` ## Spatial tables in Data lakes @@ -505,18 +445,18 @@ Let's compare these Iceberg Lakehouse tables with spatial tables built with data ### Lack of Atomicity ```py +import os +import shutil + # --- Define path for the traditional data lake "table" --- data_lake_path = "/tmp/datalake_buildings_table" print(f"Using traditional data lake path: {data_lake_path}") -# Function to safely clean up directory +# Function to clean up the directory def cleanup_path(path): if os.path.exists(path): - try: - shutil.rmtree(path) - print(f"Cleaned up directory: {path}") - except OSError as e: - print(f"Error cleaning up {path}: {e}") + shutil.rmtree(path) + print(f"Cleaned up directory: {path}") cleanup_path(data_lake_path) # Start clean ``` @@ -530,41 +470,39 @@ or overwrites might involve explicit state preparation (like clearing target dir Writing the code for basic read operations often involves a similar level of effort, whether you are targeting raw data lake files or tables within a data lakehouse. ```py +from pyspark.sql.functions import expr +import time + # Write the initial data to the data lake path to simulate its existence -try: - print("Simulating initial data presence in data lake path...") - (buildings_df - # Convert geometry to WKB for standard Parquet storage - .withColumn("geometry_wkb", sql_funcs.expr("ST_AsWKB(geometry)")) - .select("id", "height", "geometry_wkb") - .write.format("parquet").save(data_lake_path) - ) - print(f"Initial data written to {data_lake_path}") - - # Create a temporary view on this data lake path - source_dl_view_name = "buildings_datalake_source_view" - (sedona.read.parquet(data_lake_path) - .withColumn("geometry", sql_funcs.expr("ST_GeomFromWKB(geometry_wkb)")) - .createOrReplaceTempView(source_dl_view_name) - ) - print(f"Created view '{source_dl_view_name}' on data lake path.") - - # Run the same spatial filter query - spot_wkt = "POLYGON((-82.258759 29.129371, -82.180481 29.136569, -82.202454 29.173747, -82.258759 29.129371))" - sql_query_source = f""" - SELECT id, height - FROM {source_dl_view_name} - WHERE ST_Contains(ST_GeomFromWKT('{spot_wkt}'), geometry) - """ - print(f"Running spatial query on '{source_dl_view_name}'...") - start_time = time.time() - source_dl_count = sedona.sql(sql_query_source).count() - end_time = time.time() - print(f"Query on data lake view returned {source_dl_count} results (took {end_time - start_time:.2f}s).") - -except Exception as e: - print(f"Error during initial read/query setup for data lake: {e}") - raise +print("Simulating initial data presence in data lake path...") +(buildings_df + # Convert geometry to WKB for standard Parquet storage + .withColumn("geometry_wkb", expr("ST_AsWKB(geometry)")) + .select("id", "height", "geometry_wkb") + .write.format("parquet").save(data_lake_path) +) +print(f"Initial data written to {data_lake_path}") + +# Create a temporary view on this data lake path +source_dl_view_name = "buildings_datalake_source_view" +(sedona.read.parquet(data_lake_path) + .withColumn("geometry", expr("ST_GeomFromWKB(geometry_wkb)")) + .createOrReplaceTempView(source_dl_view_name) +) +print(f"Created view '{source_dl_view_name}' on data lake path.") + +# Run the same spatial filter query +spot_wkt = "POLYGON((-82.258759 29.129371, -82.180481 29.136569, -82.202454 29.173747, -82.258759 29.129371))" +sql_query_source = f""" +SELECT id, height +FROM {source_dl_view_name} +WHERE ST_Contains(ST_GeomFromWKT('{spot_wkt}'), geometry) +""" +print(f"Running spatial query on '{source_dl_view_name}'...") +start_time = time.time() +source_dl_count = sedona.sql(sql_query_source).count() +end_time = time.time() +print(f"Query on data lake view returned {source_dl_count} results (took {end_time - start_time:.2f}s).") ``` ### Updating the dataset @@ -580,83 +518,71 @@ critical lack of atomicity in the overwrite step, which risks data corruption or loss if the write operation fails partway through. ```py +from pyspark.sql.functions import expr +import time + print(f"Simulating an UPDATE on the data lake table (via Read-Modify-Rewrite)...") print(f"Goal: Add 'is_in_spot' column to data in {data_lake_path}") -try: - # Read the *entire* dataset again - print("Reading entire dataset for modification...") - current_data_df = ( - sedona.read.parquet(data_lake_path) - .withColumn("geometry", sql_funcs.expr("ST_GeomFromWKB(geometry_wkb)")) - ) - read_count = current_data_df.cache().count() # Cache for potential reuse & count - print(f"Read {read_count} records.") - - # Add the new column - print("Adding 'is_in_spot' column...") - modified_data_df = current_data_df.withColumn( - "is_in_spot", - sql_funcs.expr(f"ST_Contains(ST_GeomFromWKT('{spot_wkt}'), geometry)") - ) - print(f"Rewriting ENTIRE dataset ({read_count} records) with new column back to {data_lake_path}...") - start_time_write = time.time() - (modified_data_df - .withColumn("geometry_wkb", sql_funcs.expr("ST_AsWKB(geometry)")) -# Convert back for storage - .select("id", "height", "geometry_wkb", "is_in_spot") - .write.format("parquet").mode("overwrite").save(data_lake_path) - ) - end_time_write = time.time() - print(f"Data lake overwrite complete (took {end_time_write - start_time_write:.2f}s).") - current_data_df.unpersist() - print("DISADVANTAGE 1 (Inefficiency): Update required full read & full rewrite.") - print("DISADVANTAGE 2 (No Atomicity): If the overwrite failed, data is lost/corrupted.") - -except Exception as e: - print(f"ERROR during data lake update (Read-Modify-Rewrite): {e}") - print(f"The directory {data_lake_path} is likely in an INCORRECT/CORRUPTED state!") - data_lake_update_failed = True +# Read the *entire* dataset again +print("Reading entire dataset for modification...") +current_data_df = ( + sedona.read.parquet(data_lake_path) + .withColumn("geometry", expr("ST_GeomFromWKB(geometry_wkb)")) +) +read_count = current_data_df.cache().count() # Cache for potential reuse & count +print(f"Read {read_count} records.") + +# Add the new column +print("Adding 'is_in_spot' column...") +modified_data_df = current_data_df.withColumn( + "is_in_spot", + expr(f"ST_Contains(ST_GeomFromWKT('{spot_wkt}'), geometry)") +) +print(f"Rewriting ENTIRE dataset ({read_count} records) with new column back to {data_lake_path}...") +start_time_write = time.time() +(modified_data_df + .withColumn("geometry_wkb", expr("ST_AsWKB(geometry)")) # Convert back for storage + .select("id", "height", "geometry_wkb", "is_in_spot") + .write.format("parquet").mode("overwrite").save(data_lake_path) +) +end_time_write = time.time() +print(f"Data lake overwrite complete (took {end_time_write - start_time_write:.2f}s).") +current_data_df.unpersist() +print("DISADVANTAGE 1 (Inefficiency): Update required full read & full rewrite.") +print("DISADVANTAGE 2 (No Atomicity): If the overwrite failed, data is lost/corrupted.") ``` ### Querying "updated" table -This final step demonstrates querying the data lake path after the simulated -update (the burdensome overwrite). Since the raw directory of files doesn't -maintain a consistent, managed table state like Iceberg, querying the results -requires re-reading the potentially modified Parquet files, re-applying -transformations (like parsing the WKB geometry), and creating a new temporary -view. The original spatial filter query is then executed against this newly -created view. This entire process, necessary to access the latest state, is -contingent on the success of the previous non-atomic overwrite operation and -contrasts with the simpler, direct query against an already updated and -consistent Iceberg table. +This final step demonstrates querying the data lake path after the simulated update. Because individual Parquet files are immutable, any +"update" operation is actually a burdensome full partition overwrite. To access the latest data, the entire process must be repeated: +re-reading the newly created Parquet files, re-applying transformations (like parsing the WKB geometry), and creating a new temporary view. + +The original spatial filter query is then executed against this new view. This complex, non-atomic operation, necessitated by the immutable +nature of the raw files, contrasts with the simple, direct query against an already updated and consistent Iceberg table. ```py +from pyspark.sql.functions import expr +import time + print(f"Running spatial query on the overwritten data lake path '{data_lake_path}'...") -if not 'data_lake_update_failed' in locals() or not data_lake_update_failed: - try: - final_dl_view_name = "buildings_datalake_final_view" - (sedona.read.parquet(data_lake_path) - .withColumn("geometry", sql_funcs.expr("ST_GeomFromWKB(geometry_wkb)")) - .createOrReplaceTempView(final_dl_view_name) - ) - - sql_query_final = f""" - SELECT id, height, is_in_spot -- Can now select the new column - FROM {final_dl_view_name} - WHERE ST_Contains(ST_GeomFromWKT('{spot_wkt}'), geometry) - """ - start_time_final = time.time() - final_dl_count = sedona.sql(sql_query_final).count() - end_time_final = time.time() - print(f"Query on final data lake view returned {final_dl_count} results (took {end_time_final - start_time_final:.2f}s).") - - except Exception as e: - print(f"ERROR running query on final data lake data: {e}") -else: - print("Skipping final query due to failure during update.") +final_dl_view_name = "buildings_datalake_final_view" +(sedona.read.parquet(data_lake_path) + .withColumn("geometry", expr("ST_GeomFromWKB(geometry_wkb)")) + .createOrReplaceTempView(final_dl_view_name) +) + +sql_query_final = f""" +SELECT id, height, is_in_spot -- Can now select the new column +FROM {final_dl_view_name} +WHERE ST_Contains(ST_GeomFromWKT('{spot_wkt}'), geometry) +""" +start_time_final = time.time() +final_dl_count = sedona.sql(sql_query_final).count() +end_time_final = time.time() +print(f"Query on final data lake view returned {final_dl_count} results (took {end_time_final - start_time_final:.2f}s).") ``` ## Conclusion From 923a7b3524fb065148ed32bccf6b2dc6eb5e8dc7 Mon Sep 17 00:00:00 2001 From: Kelly-Ann Dolor Date: Tue, 29 Jul 2025 10:08:25 -0700 Subject: [PATCH 22/31] fixing yml spacing --- docs/community/contact.md | 2 +- mkdocs.yml | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/community/contact.md b/docs/community/contact.md index 9c4fc7641dc..45297fc0b58 100644 --- a/docs/community/contact.md +++ b/docs/community/contact.md @@ -42,7 +42,7 @@ Everyone is welcome to join our community events. We have a community office hou ## Discord Server -[Apache Sedona Community Discord Server](https://discord.gg/9A3k5dEBsY) +[![Apache Sedona Community Discord Server](https://dcbadge.limes.pink/api/server/https://discord.gg/9A3k5dEBsY)](https://discord.gg/9A3k5dEBsY) ## Get help diff --git a/mkdocs.yml b/mkdocs.yml index 2088c47b4ad..98e2b3293fe 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -230,9 +230,9 @@ markdown_extensions: - pymdownx.smartsymbols - pymdownx.superfences: custom_fences: - - name: mermaid - class: mermaid - format: !!python/name:pymdownx.superfences.fence_code_format + - name: mermaid + class: mermaid + format: !!python/name:pymdownx.superfences.fence_code_format - pymdownx.tabbed: alternate_style: true - pymdownx.tasklist: From a4dc74a32f508202268c0938683d448bc3bf2a99 Mon Sep 17 00:00:00 2001 From: Kelly-Ann Dolor Date: Tue, 29 Jul 2025 10:31:11 -0700 Subject: [PATCH 23/31] fixing yml spacing --- mkdocs.yml | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/mkdocs.yml b/mkdocs.yml index 98e2b3293fe..c44bf74fded 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -229,10 +229,10 @@ markdown_extensions: - pymdownx.mark - pymdownx.smartsymbols - pymdownx.superfences: - custom_fences: + custom_fences: - name: mermaid - class: mermaid - format: !!python/name:pymdownx.superfences.fence_code_format + class: mermaid + format: !!python/name:pymdownx.superfences.fence_code_format - pymdownx.tabbed: alternate_style: true - pymdownx.tasklist: @@ -243,22 +243,10 @@ plugins: # prebuild_index: true - macros - blog: - # Format for displaying the date of blog posts (e.g., "full" for full date format) post_date_format: full - # Whether to include a table of contents (TOC) for blog posts blog_toc: true - # Format for displaying dates in the blog archive (e.g., "MMMM yyyy" for "January 2023") archive_date_format: MMMM yyyy - # Maximum number of authors to display in the post excerpt post_excerpt_max_authors: 5 - # Format for displaying the date of blog posts (e.g., "full" for full date format) - post_date_format: full - # Whether to include a table of contents (TOC) for blog posts - blog_toc: true - # Format for displaying dates in the blog archive (e.g., "MMMM yyyy" for "January 2023") - archive_date_format: MMMM yyyy - # Maximum number of authors to display in the post excerpt - post_excerpt_max_authors: 5 - git-revision-date-localized: type: datetime - mike: From e5f1ffbdd222151d521d45fa6c1d38a0a9e0fe82 Mon Sep 17 00:00:00 2001 From: Kelly-Ann Dolor Date: Tue, 29 Jul 2025 10:59:49 -0700 Subject: [PATCH 24/31] fixing markdown formatting erros --- docs/blog/posts/spatial-tables-data-lakehouses.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/docs/blog/posts/spatial-tables-data-lakehouses.md b/docs/blog/posts/spatial-tables-data-lakehouses.md index 204ebc9c4e9..e4fd683d758 100644 --- a/docs/blog/posts/spatial-tables-data-lakehouses.md +++ b/docs/blog/posts/spatial-tables-data-lakehouses.md @@ -79,7 +79,7 @@ The Lakehouse Architecture offers several advantages: * Lakehouses are interoperable with proprietary tools like BigQuery, Redshift, or Esri. * Lakehouses leverage the cost-efficiency of cloud object storage (similar to data lakes) for data storage. * Lakehouses are highly compatible with existing compute engines. - * You can use one engine for ingestion, another for ETL, and a third for Machine Learning. + * You can use one engine for ingestion, another for ETL, and a third for Machine Learning. ## Lakehouses & spatial data @@ -171,7 +171,6 @@ However, by ensuring that each step is completed successfully and atomically, th If an update to `sales_territories` were to fail, the `stores` table (from its preceding successful transaction) would remain consistent, and the `sales_territories` table would roll back its own failed changes, preventing corruption within that table. - Now, let's see how Lakehouses differ from Data Lakes. ## Lakehouses vs. Data Lakes @@ -328,7 +327,7 @@ with another table loaded from GeoParquet, an extension of the open source Apach !!!tip "Best Practice: Co-location can optimize spatial tables in Lakehouses" Consider the following co-location tips to optimize your spatial data queries: - * To speed up your Lakehouse queries, you can co-locate similar data in the same files and eliminate excessively small files. + *To speed up your Lakehouse queries, you can co-locate similar data in the same files and eliminate excessively small files. * Use Apache Iceberg to store the tabular and spatial tables in the same catalog. Let's look at the following GeoParquet table. This table is the Overture Maps Foundation buildings dataset. From 72e199ac5248f25b4e572904cc373b6e47fec6fc Mon Sep 17 00:00:00 2001 From: Kelly-Ann Dolor Date: Tue, 29 Jul 2025 11:14:00 -0700 Subject: [PATCH 25/31] add oxipng version of file pre-commit --- docs/image/lakehouse-architecture.png | Bin 45271 -> 42889 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/docs/image/lakehouse-architecture.png b/docs/image/lakehouse-architecture.png index 6ec5804d0e16178e0d5cceedf192b99d57b45409..8ff353ab7bec544d83a3ff7b1ddc2f3544ce8052 100644 GIT binary patch literal 42889 zcmV)&K#aeMP)00E8&0ssI2BCE6v005^@Nkl&z+5Qbq_4#KtiA~{Fr6uCyWU0G+7Rs16uW(KXZNNkZRR0=8yY!E*N{5}hn7G7AR zru%EqT?qgHfKB*x2LJ$oO(56-000o|00026C_Ie@007tqf*k+=0KpCb000C#0000G z>;M1&K(GS<03g@_004ks2LJ#7f*k+=00?#f000C#0000G?A~G(P8 zv_!7%c`8jR2`X(l#LNhGJ*tY5S_m0iGB2&Hw6|Ul?2Fi{oh|%px_Q;v>KZ+~6Hd4> zw$naFsWhz+#U`pZ5wSjWfQh=(@ayH`#zA8d671ffs*QzJp3GBc(g(}UH5p)(38a5M{NBW&8$~8>h~b5zeyjS$eHUwOURp`-s-WtrH4FIrj)w;2zIT` zYm1xh)(5|OX{4%ax$w*^skL4OcG7(@E+8k%oz=>caQY|2rQYcHl9tbhN7~cTGk-Lv9n0>Sk`;$2Vq1u&ap( zO%2mo_(t`CJM5wST^xRVJ$(E8^8HI0ewHuK!&~k7qPVUnu;tqP`TM-fi!{sbQr@ZrgMU%kcrVgpd%N;J^ux zkU(rgNGuZSrAP&ph^?g{3KX>nXjnukEe%y8+NQK6-3xTmB(rbc#(v)I@z^t-$;{X@ zwtt>IT_mKILsHZ<{$xGtRdad-Kcf24hU@&LG}5#Q1;KPW+Bt$e9LMY%s`R&x^tMBQUcJ4n66QJp#hf zAn@n62oQG*oIiBBZX!$N%hXh%QY5)_i7PWGPx1sK6loz%3N**hlm(XKQ-n~U1+v2D z=+KxU1%l->lt8jP4l6NY>68nrI6hg%Mxk6mDH6w^WSK89T%JT_n#I@3mY7t5=ybd1 z&d?8pcmiB(tS$(^>$$$ua80JgOxW2U?BwTG;yvrnEr@zac+kBrk%%oL+xHBGBoMotiNl9UoT8ejb! z8@WNNVw|))N5T5yq;y@|Rr8J1hQ|IEy5sl2#1fdi22$5S?m8%354js5eM4wDw4^(J zXLH|U^~p6|KH+st-}C1()x^Z^->+Jnu3NaMT8+0q{Br4=s~U9+tDR2g_J4 zn#HQ;d2g*;_uQ+?JMDI~3^?Cy{_T}zD;tga;42*d=$qly>oI|Y2%moS<1??UXti2c zc3pS#w(;j)e@9WZDA-@I>WkNweIUx}MfBmFxv|Aog9SAa{Ba*Np9S{?Xg&_@r@(y~ zChrAmGx*;8W@@nzH-MfGIBqXXp$ab)ScQ`mir08WXT`9P%1Q<))+F613pK53P-2Z! zLY0+ka@AnOny4AHge{?qS87NNT?&qr!gxZcAvv^{`7pAK3|`SIf{rAmz^XKo@&tpg z9`~)r^?}pqcbXmUSmV$e?XkPy;FU0Q70|bUct2F1faa^v9)`{;Xul7wrC>Y-@%JAMzcy<%taiK3u7i_nMz)Efh*jU~Z`^+HtBqq;-40dX|9;Dkch_%` zWMy#f?ulb7zx+kfbgZ^#TOY6cZsod7qH0`BYy2Wzdx9Wf$6@qpIDQ2rhai0^q=sPn zG8n%C(rcmb%;V#|kf)c0{?l03>76L^Sw@;>lp?2PSe+1z0;&;$S>nwMZ_=`rLMAI) zSS|?`C0WJDm=#PcXAvIjcq|JRAzFFfWaQAuhNDHxby~*8P|cwlC1R3iP`sY1Xt|1V zqFBLKkN?Sw+j%j(zUQiC`^a#2^j0`<8Dwt)^Z=Mk!QBY{aR3HD2WSCw0A>Md00@E% z^man?U63Ay!fheF8@;AJ@!Bk!^qlToib6553lZ007kDYQKbfOPcOBq)gw>|qoGPM? zTeh1~63%g)49Sn~I@oTv2V5;rp<_ux6s6GXI%xtOK9Ot9p7icN^4Fm$LRQqlxhGQD zV@Xog4Xk2nxNXOty+@N(%bw#R8du^jgoGVvX&AX3)?W%gUILqj;HM$jFa&Ea2jW+l zLz~^%s&aAb=lMX_?WHLp%%8CuE9zxrAlalPOHi$fQpcoCuG&Shp&B+RHCVMF={6%b zWW%Q9hM?IrmaBLST}W+Il!g+yRKvnX-Lmn4bla@sfVys4q829eXhElunj=`p?VbDa z#>8$hFx_6eX{WX{#~y^;Lol@j(8Dmh7W~5zpQ}cP27v;B7##uxG6X6%Mr@ABiEDnVSvfG=BP0?D7WeJj2W_lPkbs22eJCm* zgjgEw?s~8rcSaD#JY~hs5ZM?NIwKuC1=J~!2ckkW+64?0dLk$q2Hcz*VnDpx5e~65 zAcVN~rVSmw@39mM!iH$LCqlv)!OF3km)U)I3m^d4w6nhDpt{(tWIOb1q&^o`=DU^9 zbM)cefH2%susxS;HSJITzW|}gqu8V1Q4~-JDB_i7WMh{=0N5kQr<^Y3`nxH3O+icS zxAe^xpzuP?Jw76z7?lSS^2t%%#8Yv!}gL z|BZEaK|D<~Z+;V*I@H@6gKfKD$2;)o$I!hW&}JYekOGi0kQz`Gr~%Y$QUfXjk$~iR z3q>Ka8@9X$J3oXcp6}V5?V0a4#=fvCSxVu)0&cIfHV7d>5a_x-Hxuy?;5Fmc z3jzx93J3xYb0rh77ANKc-s+0tbl&biR_H%o{Pc9G??madfl~j;;zy@T{m1e@42p&w zbcSgAWchbEeE2Y|3;0&UNPN}3^krnL3XeStJ3fF-FLI;i7a=GDRhu@><^&6<#Ur%F z_FXoBh~O6>aH0Dd*!C`Le;IGv*jxMFi+y6{la#LDE^ZsUHrBkWtJP#ls@%!tMz0Nz zjmD;@XX=`ky?uM=;>Bb#nMzHmin5%~UmqThja-k%s0kxG)-viK1AoRtt-Zzn?!BmuU^eBFB_&MmBl|UUmm=4DVJMR z6lJB}uCK4JeYF3d@O)ph%$ly16**TbEtSihVtIC9{@BrhpN^cGObfS#{j=d$ri26O z(5q?uLbmwGsUNR=cjV|qDz#J*TT|HFLbb|XN7i-Qao{f6&VDsKdBq3>feHv1!x%a+ z#Z4y{YDLV8T)-l)0LBm!nkEmmDx57bPX%BtJ_Z0v385rl1V-OykpiKVGCd7rgi!mt z(~4Ib-v-wO+(yN?{`m&^+tA~kuTkx>ZNDswYOX`+kRr##(iY$bX{{CTajx8Niy8PGz?0D zTBTA^RgciRss=$|G<4lCY(gYSq=eeG*)STaX|mLJ9LKV}z^|KTxm@;r&vRWr2pXN`k-llt| zXP;!U?-0WhP-GWmQ4|%C$47pZUj={ly6=iBzv5MYcM^=s`~DC`()13nMq~|CR|9r)Kh%tboF$4rcTxSt<}F{)f21kShMjS?65ma zeUENj%j3E6dzY_U{m=jRb5<`~|Kw9!HWfijmF%Z4Ra@BZ3mK~vDLdJ!1LAe-rQMm#fDHRGp zm;fGr_~GzRUt6)mfCPADYgNxJWOdwF zfbdSfYD^CJlAG-T>Vx!LnDz$fT$prM7ngr6$oCu}?iW?lS&wiaqU*Z8@7HHi?d@&a zz>8kA*ma$$Q#-QRhUVtxqmNz`bKOijJ!|F+L|n9RK_Z!G5)HFw&FbsxKjDO>sZ=tN zjAzoBV~;&%>C$6U$+~i>kW3_I&7QUN_~TNklny+{bvmX_S$h1j*@k*R(Kj&I*xWpQ z#*BD8?l{g1!z#x6`k`KM!^9N+r5!F-XEyKNwW)i%HbE>|SC^`jfG8yag`2>anaREX{qKh=9COSuGiT16Hf`FB88f(_J9lnNOUvP8b%rP~;9H6r&_tdgV^IJZ z;G3WX_yk&`wgf=PvV}mSvE&;b2|iyEpd|p{E-+{;X#<*oVTLywgJ20nMJ58DnTsN$+uE@d-ZEHDb5-x4?Z(sd_#_DK%wk=P^qb@9F8y zWYR(?-c;t#pPQ*mH#fHsVtsw4t*sT1c$g3jnM}r~cTSx$bxI8IcR z_P1}{y7i)qF8by-zj+u~-KJg4>b40-X0sjH`mAtb`})iTfi-9wT3TCXb*_2j(H-ly z<@<*csbuTSwuL7ivtaR2sd!piWB`LGqNhgNQhuOsaL+&?_w@Fy3p!`8@^D1u&5WM* zk5vFwXliOY<&;xi{_>ZfdFGk*_4S9`6 zK!_*+B!U7Gh~b}FAh0L|NdVwWB1^!sB(MN{2}Fp11Y%nsvSh*Drq{=OwnoOKS&2aE<#OBSLAA0Da^UpuOv9S>j7Fz2Medt4f{No>a zMdIj!iEAL%E94&kRRcW&^$pN;ET9I{2ZSJGxGe$7QQS@dH)Mek&Mvzzu``wX28^v- z8L$?tg!mMwpOGATx@Y0wG!gOO$D7Xl!ng zQW;A^h-|ir8=?r8aek?>sRa=&5%Q~zP0h`1JUCq2`6}Dg9PQ1GeBIKDLJ|O=sISkq zw6#m6thEG?$QA7Qhh00_|OY zEfF_O7Lu99x=gclWlwG((E9ls#mK+g$?{G_E{zy=y>R{O(m zMlr^`@|CarZzx$Sg~UCQ+DBn7qSA$iw2B|zyJO3%<5Qp=9y<)*PXZ`QP{RZ zJ@I{&b|Etx+y)3Li4j-;l>^0nMUZ(ALx4j-0{}w+g8=W&5Ek$I5( zt^zpRSS|t;K=Z3UWamPCvT@*+{JJ~L1Xi0`j9txO{%^oclYX@f+S1>3L?`f$i+*+$yJh)eJdk+wick}TPhrV3Ng z8k1OC)W^r_)vKWj4Gj$={CYqB@sBwN`P_5QZEI@-fcx*i|GMk0gHbr|yz{>M-S4so zeB3nH-uwVcOe8aoidix!hABAW{#Z)1Ylp+C3)tasC#vrdSRoYa_l3fqp;FEdbj)>+ zkgSvlEfug(E|-)qfJ~1MXlyj0k z|M|~z9@07IoWrD0YyGQV{fg%Ri~?`u|Mg%01*3onNlA6CEgP9|tuZq0T5W{muq*+Ij7;0Z(veK_aDH zNVy~s#zigE=iloYTf25GPZ$=sXMfQ@{_&4$b>eGZ``Tj_u)+AY+iqL3WCeS3}RrABswwW)*-09D+-VRjbN#A9f3ev zPy}Frq0yGmD_IJx0E$UMp&>?K5K>vFnLL9z~!m2EUVKx<5%MG|U0vw6( ze8b^S+zWU220%|9&Md+)t4ChoZ7jt3un@T{}Wf-zyp24n#-5l;XTS&A53 zMB#{N+^Izn$pVo^z0oQsCRJRUvQK*4P5p(MrRaHHxW#sNcf&BIRKP@>8Q%5RU;q8@ ze;+Ei_S$QWF;`xBWpzFZ2Vb&Jjpf^Kza7TJ)mLBrrZ>GQlgW&>pm+vE0G_oVq#+<5 zHGv`IURy#GC2a~mdYTMjU_l!S2uhlW&^KTKJPpPY;$A-(zmyNq*LFnetsr7SAQ^&U zfW^RC8fQB18=DV;}n% zf8{s7`OQ!PPli{#;uTAmE{)z5vbu?YeZK6O9Zl>Qu7ISXn01@nglDnN6`mCdg}wzA zvT@pFP10c+2ClR*i6z8@A|gmS77>#U%OWN%00aWl4p<8r1+D@BvP4M8f}kko9TZSP znS=tYg<~9ctY5z#s=)C85wT;daHwg77~31m_PkJ-xW~x^{@^qBw>dk@vFv>-1mxD#49a9E@#%RFW>*b z(8|Y(y?Z>crI_-`c-9)NP02I;Ly#{LNHMKlo_0d>oOb8+^W24XgM;huuybD~<{6I=FH@jQuF4`JMOsSSb+V((`W&g`44Q$JqF;$9Hj}AL`!4{l`40@(dW@w zMcMc^yr)ndw0gwQexKThfhelL$zA^NT$kfMg3}uc1g^7W5-fa2@#O%(Ty81WedmbBTi1|`_ zCm$uxJR!btcCsO>;t8o-2?&GzDm@^eSI#RJ%J!mSIbZIEq)#a~vvh$aCjySaP<{R!S7r(@Xl?6|G9K}G8HfQ z6qS~W5-61tX`-7S+4Q|@q}T^B(scX%}wBuY29=I57(*hJ;jc z$K2g_=M4kbun0n!C`SjaG{sjp{l_JZ=lVuVXV007cr?@K8c zTmi!|b|CjoE{e9Fgv6M%HH+PIYq5)FGV6gX+wn#Q)pxu8 z^{=jYTmnEYADsS@_W>2NB*>9&C32Zr)kmoQ$R}a0`L(6bJ5x5GmcGM`QyGP zHfiYw^$A7NO_n$3cdd1^(@=;Sv8z=)`L}sq_<7h}0dK$uGR&|soJVqn3wFe`wziJp z!`-xL6C3@f#j|W|Bj>4WO>@kY53mYP-+L}Hl zKJ%H+a6d-o1VbrRUHpaB?r61z$-@#!r7R1cZ^62bEDQ{M`Qh&C>aud4y9i6Is5e{j z%#10YSvxfJ@Yau>F;hyVjj;&P9~}kOA>Foe%O|#B&k3`p#EbKxTn5>;A_kv8%GR8zQXHkq{QjO|_MdUB~i*qh*bQvWlE?&GC zCJNHVdKwhPKqf(?Kqx?v!<@?qqrmmG}la5F@KCf`u4nC0OwJI6_^P=zq#iiw@pI(Y)3XD*wzN~Jw^H>iE4xYQ~uDf7gNGV}VaE@ds%%49$ z6eh9eZrT*wa*Mxmg)Wy1+Il(=*1u^@-_41vXb(P;-*Kyin=|P@p?*hU8!XCPW9si+ z*ArLPGdeKb0&czulmnZ)_k3-u=~>coroa9Ap!~;l-L1*k)!x9=^x(1?9p7DF%olQq zrr`MlUZB^l)qnWI&?Czz2oB`wJ`!B;W7MPJib%X-gmQa(I~*9%BEZL2OjrZM;YVFW z6OCm>ShY}y=<&5cz>Qc2Tj?q4t5F#eF}Wx)R#_560>U9RR(H6k9Q#iXWPZEvzWaDr z8f{Uo#^~Tq4*G*_50$*M%g3^+5ZeJuqhVLp_7t-U-LM zb7r|~AMWd2GoTW-J|l>zt-;0}V#H$t2MR0i?QTkolTTEcgn8utJvLVY&=8ctD?y-B zSzNpz=ETIYGq9tXG=c@h{4OM_)v-GybB?XST%h3xKls6CKl|Be!ZG(}vcfSXye+ZJ zcShT&zW(*EbL0>Y;G&4kllZ*Fu^FDVmd(rG{`R*y?s(0bH83%-&5V=4IbDcT(th{5 z-|>v+kA9Zlh0ld#BitBF?p3dP)#pCB%RbT)A@P7ryX?5!yNL^jvv}oic3U zV+ZbtCoN1qpA-ngAa~$~ds}3&g9$og5h-je?<+7Ofkt?D5xLcB>l`pqnhRS*=S{ui z9q*WIyyj$!82kC$=FfjNcFD#1Qy=T=>K+WV@0GiD_ud}F2U^|#80@`r+Kid(?8{^p zOq+I9+mx3K>?llY`tu$rf$1vM8`1@_~7~AYp@1gg(x(ZwQ7 zZN<;LJ9)`D%32#}<9FxF{iW7stV=p=t!`n^EA*EE4d?(&P%$0*F&8s&Y^g`bHAKRI zvBe%DQ;V@XB#1Q2kZiF9_=6w(AXI@1t8zi6YL61vw*J(oK2@Ewv#J8-ZQuLe_x4-3 zl}%{8EOLELmLnI{Ti^OtRx+YvH%CupCUf8U&UauGn7#3;%BkH4_gy#~3Jw6SRWa^D z{OM1BI?~!lgQKJuELZ@OgtcS|jKxqH2Mf|-RNLYpnO7K86Nc7>mp~Pumt)_&_wzzwwQ4@XViJbz*kSPikL}U~`532k(caOH5PqP|qHdN|g)6UN6{{ zhq#k_b!VeYB)Z8GK?$L$q2Zh@+m;V)+g7HYUJVJi6zD)(DTNZ~`mkxB)GvB27Wv~A z9NlYe08j!*1D@8Do&HQ$xs=|H-poOpFR-U~upkglI?g`vGD1 z5FuhfRswpK0MNIjG|M*3e=(H-p0Q<(#)O7C6vP53%RM)1kqm*Sv22K_cG%1)9I}`M zB8u|p&p!KX-Uqo}5>&uD6xX#LZQy|Yf$b@wz_t~b40JJP2!*wPL1V_5*+!4;B(JO? zKrj{4MRtj((aHQ}GdSEa(cCmBuB@G>&`QP863k4fkt+g5uO8ro?*c~D;uZpG)??xk-P^1k+GTX8Emb=SXtR(%<~%H~g>S`n3E@m~@)O0jL{>cPsrQ+ejcppCDl0 zF|~sy4g~h;M+IhClVP8llrrK0V>3cF@I*u(CJEit46nSfFmDl)t%0Gz-l1Gcmzvi; z(tP)WYA_E3vgnCINfyg!DY<(v^{2mNw>&!Nd3`0ne|L|)`*z1brVToGu z#NoxGk3JfCjd(vg`skx0)BaC>@)H;aPeSgaa@nJgBa2Q;;u9<;!#T(Xd16>=3?!1U zphF0x+#mwOA{YP-Ae@F4S2F_gJsV5P);1@%+YIIGaf=<<=qx@eF?XiixkF=0X4>6y z$&j_hQqa&EPo*6w3orsmhCg#pj%GKD>Fol@7^8Wp(LwDs^uj;SYb9 zCm+un)=S>_#y3V3Eu=&tuDa?f_Ed6t{b*7~XD&tHJ)^pk=jcPu{ZY8OD|;wZovX} zj!g^8Q%x2MK?zdQDuGIXD?rK0Wz-);Q;}?k?|@Pxf1oQsL4;aCyS-rZGsg@>$^e(2 ztv1xTt~+)XZ6t>|HxmZ|m4pMGGs=n7V6j-rm-_~b0?m98m@T{RdTjkru~hG= zQP%!*-7~wM*)gLult9hIaVTHT=khreFr~9ppCNp{8d07du^S&u(>V;|^{;WSgaV78EtKtxPHF2rs(Je^llRrq^E>Ni zrItTXM5GJONO7IA*1S3Goozw{@f9TB$z(3 zC5&y?>=xnoGLMWR3|Ld)51s6HsYWuHj6OP~iDTgSygf@0fgv;&!v)t6 zSe6MU-i*b41dEkM`wZYq20>LLO<>^pgu}2F?AWn`sed%pc@8V3Ce8!cU=wL0hzOH` z2H06DbZy+QjZJBuS1OkJhw?*3Z^7*LSse{GJ-Xp@x8HmB)0-aM+4YyFHhlcHdmesz z=aQLibLVvK&KHL^ZJF`l12eMq^VdB|kFVa|n`>yyym(RPj^6yoZ@K@M%N~7nSJ%T^ zw*SvR{`Kwq9#6aO35%wY5Z%2)OdyNC?&<6A+VJ$Y{s9dH&s(sjap;I0TUl5U4MU*D zKxAB*GiS~uHv@7o%%8GAD2K!BG$B}$q$QC;gFpg7fgc;#ljz%-=-UR^Lk^$gmSp?E7D6kT17Q4e*lYwmtQRHrOb2AIPhw@T0qWodC zA3w5mMCnDXc0W^wA0wN$|@`;F|f+Zwmmk4ME_c*iBS=O5dm%A{N^{A zu|+O0elMpEaY%c#)bc-g65nO=anmFIJ)HL%U2;YeM@Vq4#m0NU0W z^301`jytbmV2A&iFZKtXdH;VlIZ}Z~kQdYEB>&f?nP1+P|JDB~%v_K>>kTcl7spTp zkQ7j_AS)p0auC*6ulG9};DiN&d~3KF$7&R*W&lx3o;Xz1DA<(6+CZcwgyqynr@?Y` zXSJh~KZ55RXMk28bj>kyBUBSc^pmBO)hj7i8)1?Z`Bga%FQjg~s`9%+3-f+c&!m+6 z4`7=dpU)j8Nig3+#PPn%_{7c_Gwue!-dNu3Ij#ZP2OZB|tq^k1Evul_ z!6cj=&-wU#)I_wJ@tVU`llZA0+hRA_JSvG0msX+$U>FGs03>k_1`x<7gqXChM4?2& zFLnRxq2l)K4$(g8Qos$30O|K&|>Fdw_rNhbVY$TM17{x2X*w-MsF z4|R>$9c02k8zgw@M2zh~**7B+CjM0c3)LZaQB29`umG;eAIVX|ARFFz9}V^3=p-Lr zJtsm>*#*qRE*$T99#gK--(|81ar|>eRJ-8SoXKJ}VO2_TY#$ryxO7YP386o!fZfx4 z-rv-zQ(-bN1jd5200ev8CI%6s>cj|CP2a`>LDWYDN%zV$oEU2@a03(qQAi0^*Uqnt z5`e;GH@f6>89o?(bg{!{D&WII7cN{l+LIg2aF{&2AQh@#ezFY29LN{S=Xdc!JN$jC>;W2DpRmsG%3I?jz{jV@ea z*yq3%D^5A(lreImIp`K917Aa+Nm~R4p&^tQ9smZCtO3V2l=m?2YayixfRrlIaOlMpy|1`nC{I-iM;L090M>WN$+vWAgzd{o0XYhY1`(UTy5Cu4XTU;5~(_J@Aqe zEvj5si>Y3iP{b$dEKHZ=J#j~S{oJkDxSFFwTT5Y>G%IE z|Lo>cQOlTPVhREa#iDMg)91ZZz2$W+vuC?7V89Zf0J2aJi8x;IAI*zr=dZi9_>~`! zMK_gDM(crmP%L@PY4iG*#@~E)a{g3n^PcgQ4y^V~Nyk!6^MsH!i`}6@RDG8%aI8+lt{0#U4}Ta%osZ5LWa7xx+e4e#xVeYc zT0Z$W`gCS>V|aqS1NVoGKf#y3yHckw@>~%G6lTH-%D}DPGz4{6Kkur?n+Z!~jH) zuJnO?pF1L@j92V<@PWSVn@j1W+0#P;>$-Q-!w(hbFM`G<5%>a;08k+Scv=&I5E$OH zmWf?Z@&j2eJDx}JI6aRm7aqK(!8&0`?81IDlu~?97_S4|M=`#miJjIuTK9NCgUP^E zHlax7tgzM!1VRG3TaQ30h{dR1iLh)50H6f2fC9(>5hWC)m9^ZvHX#6p z6DA~piRhgriI8@v&ZSz6-C+RRM}GeEpI1lGjh#Km zZO55Ce+zua@dE7#6vb5BJ@3@mOHaTp+x6O=y00KhzD>oTwI#k}VXR|@OgRGb2!z9? zVF8RHB&2xR$(iGi3f8X=)^G9$^ClpO3o$)gcl4ZibBj%q55;m2_$H9rXkGM+&h*Y{ zi&4s2jNSib@7#W@s_rVaZ%wbZD zmif(|oqg7-)Q`s~%qhkeasK9Ww?{3qC0ZwZKtP1I;W$!Hq?5*S=Y^Lp)ecX82+G0gK&M&<&H;(hOaqNXsK!c=5L)dtg z8aF#%M>AntJ9HoKiU&t}*g7q%=jd2Wv0wEUIT8~-%-`q$>pf35$zS8rSM?yYy$t+4!=K1EO7m0dcC zF_v>ybHmn1Ge|pH0;6+ny5SuyB0n&>_(#6uoPA@1hkLV~Cb%>si(f zdl_3Ibr*O$Yh67P4eJ|hxv;*emlJW1q*idMhAX-`AF2M7EJtP$Bfw*%5gmB~Hu7!dj2A$A$cn7Q!#bOBw zZO`zNrT#fL-)}RYr8dt!o8vdD>ntCth_x$wAAg|#*~cnBdUj~<)AM&eG;jGgD}5#r zRVfTwsD#tS=8I#A-BMLCR4Hv(+xO(-*FF2pzz=uz{$S_4ZCeMHEEIF{Ow=3BG+eH; z%x0 zqC8@ql``LO?-*^?hCW^?6scrPBQsitw&rL{5mH~3TuWY1+ZaUkkvF z+lLK~H@E#3$N651Pr!SB?AS3PQsQ~AxPUnnfaB<+paWqfQyGJja+)J{-mw+%OVMDx z3t3S;i7OeQmG99`Na3WA2%lZ-MeznJMG}JNgfDBa6T~wKp)+-Y%akRJpVGrBgn^IguM`yi_Nv z&Rx5}4%cn7PXzTQ&ABB(yWO+KE=H?%h=73C2o%2ym;}VxSRlY0Cr)V zA|H?pR>I3zfR!*Z=Nv>AcJ8f0FlJ%Ueb%u@edYu)Kux)U;wLT)cZGnMLEz9iNAF6%yKQCilR+P!=Cs#U9?Fu?&?mSN-Z zz&2MH?*`auElY_nF<1+!^h{#xeDM>-dl%`vFpcMoTI+I>XeBvgf$De?8>6FVvs%^r zn`b<7|!M7s9~GQ_zXK?d#wEv*$PM-hDW? zKYjJjW2etljqfqm8!M3*gjYDx#y|Q%oc(-~_yI2m2Ktqh#ya9ed&`w_r9}5?wFcog zcW6KiF6di+XQi)S<|a%OJR8576WhrQO~^Oz*|Y2UpDbFsEMXe+|DGDjtz?ji@GEA_ z#Nas;1Fl^u)oAdHjPeq`mhijgVJcQ`r?_Se$LuV;i?MX-dX_<2S{=K<+c~ddVI(BH z#VOG&vgyf3;?O|Xfa=&c2|XNbvncIL5SAUsP~w$>t?0&+6PKo7A2ki1Rmk1O=uk)% zgWF}^_b@ZJCed?-u3PuysUxqn@vfBv zciu6$Xi>Q`N2zimHR_cJXQlDRn0n2fIg|bCgNyGS9zQv1zkUD0-A^o%fVi|Ke8cA% zk$h=pv(%-NsmYRotNi5pZ zaxGjn>%DN$oRl6@%^B!<(!>@xzBdoIf!48uY%!GIjBo9PJL7)G=nceX@aju7e>boB(Go z+K5+o`e=G7_3(n>;Vn0wJY92tyiluGbJJ&RZmkNc5<57vr!DzA;P zW2u=m*__2oZrHR%OZ!z%nky>M?i?yx^oOJ))ZC`1t4k#1;Bj< z!Qb)bi;{FwI^%8O-ns<2WJr})$_u5OzO?Ya5YD$bQ62B|GVxw3mq&w=1+-yLh-(1oKo`Fc3*qo~g3AM00I>r9$E<^;ZWw}~6#$F4 zwqQ3HXE??*(;j5?>eXRorYK6vfo1DPZr?4A{HjrQDsf4PHl27^N>W+1m45&Ir*E9U zAUU}I?CWoy+yBnkU7OdhS+}kt&bxfvNnfFd9(276-}%QI2jBR6H8uSM$s^yp@zHJ9 zm+}cw8xt-!M&`NBjmtCBsHLu!)#qS=2SHgTEF8rW(S{Bfg)+i; z8)KsNR2gJ=nn>P>j?w7VB2yKQ721YT3`;@K-XT^U2_Jz=*ILVWBXfg`qgK7pamW zv;?ADa=?!iTB^(%DU(WB1ME1v6J*QVJgc5RH}c8R4?h0z@P_s4X%}B}iO8e-T5HiH zGe2Rylb~-MgzE4FDF=Tm)8A%JmykR30RV^b2LHhpL(VY~LeIqv#N)3>>($H{+c36@ z_8UuUBzPyr#)rp7_U_%I=M5e{c0vpJ@KaARrhoR?iGQCyx9&Ul-Lq+9rMFTO<&lfy zTX#Ns)8ZRm-1qXkr%oE2t01SKu z&0`lJkH|SJYgqJ?ppi69DOAC_INgszUWnX?V&0_^FHM2FcqOEY&%d?4k;}Sq%6e~5 z6Dirb_R&E!te3e6$rGuiDO5d{p+<2mcVfM$JCWwj&*o2kg@uK1Sak_bq&*FGxDYWT zDfbWEbl>h{pZ=w1;%u6vx-11I$ZJs)sgq;p`{vpwb__4Ox$pf0XFfYo>RI{3lE;2* zFOH6kd_FQdy8M=J%^Mh2gZ=t=srTUfE7r)h_bj~o_CD2^5Re+mOJkPhE57dur!V@SJQyLJ%>$dZ3VuwouszVL&EFs<(v zFga(uR9+bGwHMxb^Wy<{_mel!vozsGt+9+K;V|+x9Hc4B3F&v zp+g6b-*#)G*?9ih|F8ycxN)Xx?Xr!NDDGsLgg^j@K!65AV20E~7KSPnW6U;E+4?No zNn`p#xypnmJwQUFeRD`L8~@?}o$iXcJ*+7Lp(!-cueV z&oIkE*3HuupY>?poxeYP{HuTl00xX*AB|(T);_oft&q_3RxKZ^)oN<>eqqZlBHE<% zHPo+b*5I~&hu>O*>Pm*|PvM+X2cW-$-9G0bh`7PDDG=8sZZi6pK{lydqdJ8#widb7 z)L*=~pbXN5iz|y4FPxv7pP#$1I6q&F-SEgrtv)m{F+SXC5#o+_5xcq*!S!p{7QL-` zEpYA8aJJ!MpRDFdo-yPifrX2^%br}cFnRcIpML&VPJb|GE5YMa&phXxy>M>!0>%IN z#+fh=g_9 z5Eu*<61^ATG{cn~yB=0E?0Eo?(%GpAUchl)J&funQpy>c`E z_{Pr)q)RZHiJ&eYD9gnM$g?!-O47;l6{sD#XX5T}Qal0xgTSs&li79Ks)w^5d(Ecg zq`iug4Bt5nDgbqDDCH_pDkTs=CdRO1yI6yasfuNs=a=)um-6(KV_g z7mZI&4K?b`*6>Jsv^CruY}oq^&bn4D@=2=batI3`09h}>@Od^AMMKpX zMf#e%z4k$8vh1aw0Doz@f zne=*$Ns6bYXYV5<8j?)heK?%njgXAx77ibx&{q7lPr zjTK^CPKdfk0|^1ZG#M1d{bxoIP!JJfv)LjL@*V&z5^O?1 zNHQOiUaQ@1RO`Exs4LSeiw3Z}6atV32#7(%NI+pPz`(pw5~~QpiH9_Z34&e%ij-xr z2%8{zkmAZTmz)_C0voM~HyAqs2FUap9x!&>wtldO;Kq+lf92b!pE{c^y$zg+Sm0iW zqS)rbh7E*GZV7}mOKj?hrBC!Pq{kr zW^(eLKdXJ_SGOBI4+6V>eALQhJI(hT*n%qow|v|rKLWB@M^s6M&{%7Y3Q}}>$@^!2 ze(u8D#l_{hb02iltli!>HrlRKqKS#gv5Co%(cx;f9>wwC4)hyFhK6PiPpgtHEvY2u zIL~1d2oRVN7((%J0}KXAJ*Y6{BXTLo7&InbUgTcy=A+k*4-Wz2fI;~bK`0iP1iT;$ zRv5bm0JzxX*#ID55eNlQEN(FNtWuB=%Jf=9K(HVNg%AzMR*O+A1tHLYum9zS08|d& zHS>*=AAbM4=YM*#nfAD0Y)o8XV~s)uq8Jlbqj1ycd{MqHvH)ONC{0XYVU}D(d=h-f zQl%2Bi4`@#G3Q4j%^OC&Rjlu}eF zbX^~%Bcid+7z;=~2pOXV#c#g(#_WgZ&YgWPgm7qTO7p;#=FzC`z=4CB5jfl$(G*E* zEdauHpp$?5)4P{)x1(8&AHL_VV@Iz~-#xRW!)KXor9#dE(f{$>92M459HX(z=RTNw z^|flf_S+}EI6cthkz>G#LWkO<8P)P z`gWJcI`fGc-; z>Plt&*Z$=2@BCf$`ul*Q?Q-ygS+RZks7*~xl}E8!sy#3S9|b}%ghZtTV}wu@Ap#;* zSam`mUBtT9b!5^-=&Y$mQ5@OGpd)dF!ShR}{^!i;)3X;Y7-PrxPYsU@H=09JQ~Rf< z5A7S@*B%)es@JNKBUHe)pzKKB+O<8C`*O#zqksClzv-gt-0QE*z4&5h_8jNAA+nLv zAa7@j;+Twq5Ymea=ihk!{7bKd#ig%(>DRw>=k0ja26y0SuL%$gL2QHotf&ZH*a}vd zU~PFpM5q|HMk17$)(aulRsoWY|FI+$;>d_I;D|S>Vs)vuX(3clVso{Xd8!P|_1=Y; z#=(iZ|NQ9J|Dk%r{i_#4cb>aT-E=iex<2henua9lC26KHT%t&OX_936G0BsjPr8_P zQVj=t^Xcjwz{KrG9{c;LFMr34-B^&z+ufz_>gUbw;oWMzUf0ehI*79vv3lafiNUM$ zQ6P|V_8v+(&6`Ndb2LG@B)JZSMI{+$ZERgRGS)Lhu{(9@#eaMHU$w;V;DM>uaH~?S z?Q4%JS2x;j54VOJjXD_v00omOJEiON#naTQe)I0-x7yXP#{9uU51gHw|L#Bj)54i| zmS<;etEmM^P_0E#gw7Ho5Gu+tnl)XOtq=?lID{`Nt&L4Y;~J$PjNu;X+)aaqP>LspCtpJbmH6PA;DQaWx7i z;yBJJLSro=APTH|BNP@65_0wl<{75BM3Xm7-uFoRwnuII2#_mkfOcTU2gMWp`$IhZ z@WVx5rvrJkS(G+L8JL61AOIOl;w2^tK}-+{Y`h?>8)^UpP=ZCU&{QEZ7A>RAH(C~l zzF@$RXKoufoBkMxn3Q?dSvlkX% z2BJ82kKBLnA3yTY%=E#Xxv{~m|71ZZk@W@{n2caR02UAH5M~P!6BrBzO=N@x8Gu*? zFen%qnE+S}a)mfYjtp545GKJQ_#+SoP=UITW5bRR25ELX^|^a6wnMVb>GmCe)V%2{ z$@@QBdHdP|WSI=v3J!pM0{M)-|vNTY&(C;0zdDK@fETlBfV=1Vy;aLl@i(k%^sE;QD4A z*mIs|d7l5^`RC5ObNa}U>$J0Fz0uGntNSPS>rrfTs2SI)##jLs20}&#i`!#F)OTuB zciEFRAnmRZ#z$J;`Nr2Dz5m`Ho_hJzTc>|MJ3F_$((R=ZeCqvW7I{Na$fb(J##-m% zO4P15Cdb-G4^Q8H{O04wZWwJ2A#THsJ^uLP{n_PY58QeY;kAF3*#`jGY2pbTTV)zV;c;=uh2us-97l{Q_l8CHl_eu~H`S|pPauD;tXqUx0<-@jj zP}&WOT&Bzg7G4Ydg@<(b0iL*owjm=U$M6nTZ7glb20R?AD#Mh)UjQ zkZh2wkYmK6eU*5>+Uu=$dZ*uh^S@tsq0wwL8bkHg$mHb2*x1C-@Myi+w6Q}nEWj)Z zl{teen;>k*yXyo@wQl|9-S-}8r_fZ$&);b4_QDKM&Y&YCT^7g` z%$$JW_Cva@)@H!i^*dLgga35^*UZd}rrZq-!B!xE1&E9oT>$}w2#|z81WOiM*54UK zLbMj0GtOB=6o3%8m!`d5Z)tJqt+(EK`P2*Kv$6K54#Jq2+`n&3t6zuf^+sHYOGnNC zBA}8;>~_3+LD#9$pLz7^-S-%2S0av_uAxsgov5E1#p!p`o4$K*y@)`_o#c>ZdFFw7 zh~xnZ(k?-g;C-GOPKenAFr4}hQys6>TmTj#Tz{c?8*&m-+_p9 z#^S&hTq(yca5qDXU~;DXf7v^`+@^sbj(c})$8o+0N>iZ<^8!MG6O@Pp^aSpye2<=_ z@4zEPjgU}*6162toH+3^joVRN*ZHtbYA5r1h-~F$HJ*?E%&uiAJzw}f4q8HE7p|zP z?)$1QHE+V$>`Tj|(YW95Uta$H{N)n}yXnMx-fFk^_d(Y+oAq+NDU7ld+GrxB#2ufh zoH2n4qWi0F!h)MY7CSyZ&Tab+QE+l{0>42sO%4X6PqjFy&2z0S^w3TSQLC1{k@u$Y z`h)nWijriZ@P{PvzWP>ZnjsaTbG_DR93GB;TnND;rzv^);DsjCvY~U%U$(}bcTFJ} zW1&jyxzksBTCHLjWt9uDigcz}K%lKfXWNZgCQI;FVpUgklj2)!X@Odc-vgj}Rv=(o z?Y#Tl9gVe;&PQX#9kO~|XlgiYf|)m00j+fV!+G#d>%bjc^?GMt&${Q|dcB@un(e)I ztJSL2>(y%AG|RfKE1ZkFS7!117IJnkp=YmD&6fuULU3gvu=fhT-EJ4>b~y|@P@lGK zqp*VuIuewgJU{X1(?=CT`-UmOz$cT&)m+FkWE`?*jIhf(sZl*El(6$Je*U`Z_l=Uy zyeXTESlN_9;PH2WDNN;D;X(+mY8u>n6VDyGgW>SzW^jG|r~kLFtD;t`Rw|}pn7XcM znl1#F>=voW`5j$$r|JOdy5nb!+J1YFbG|g>AlRU705=O1UT*79(AxxShINvUr8g;N zl|pDxst2khUXI5=0$<@iu>5 zi1i1n4I+YKAp!H5cRR{$Ad=RVYf&|3u(Db9=g-cf@LM@y6C7R z?2>n@r^IG)vcrxc!@#p5pd%HrsKW(myEHijykg60ZCpUDuAhKJ)3xkJfNROkIPr&I zu+VfM4fFkgMa1~wgSa4Jx12{^ikyr{Kq1?y_$YX!m~9YyAbB44aTrfWC#C$y-W?scjYClY^=u)VGDxO_ zt-x)NDa3IZQYCcCa3$W1IQxbxgdUobNRT+^W5?sa{Sjw98@gy6{tc2Mpf$_iUx z%Ocu87r%e`7B)4D}6zv2%V2S=7<|GNHLoy6@UYuk#7sZ-T(N3@f z@}<>1!(cc3;KfOv!7hHV_7LoVyy%$8HrV-4$n&Q0@sh(373}sjd_4yWDc1>hS&2Py zZA|1D?8aBk8=`{UZVJwZDTU0`f?aOX1}4)HIfwukBU4>ju-nb6&Ub@Si1DOs<(*rw zi$g8mn&h(xj346b7A-#nyR4Z>DX|s$!pP4p*p2sOHHVOAu#3y>OdWz27)GgJm+A10iPc5?6ms-(+$iHV`3;TdpYes7f*l)*aTr~6 z<6sx#Q_)Va%Xj!j_khw8fv76`W^@i;92zfd#)Uf9 zr)~0yM7AC$IlHms@Xf_j!7j$93+)8E>_h=0My-A$N;o+&NK4dKHJcPrW44LUB0e<} z5i5&w-6z2=-HjFO1iQSB#P%2CK2ZHe9Q)+N_?P2J*?q+u-d#6tW71~F=6&a%Z^gzO z;%uy7m*d<5WF?^kndWJ922)NbAZ|>qZBC1iP$*UHsC9EC&vD>Df$0yY=6lX|Vga&}PS)(q-Q8n>oSG zHDQ8XUW0h*%kRc%&xS1fmab2x|47kpliLK-V5iNFZI^#@f?aU8zhIZ?AWnM)TmZo_ zPfkn+J4L%qrk(VAIknlb?JO}r*u~#;y}4kQ?+hCwc>H@qmSYFIbPvU)KkFP}dA=C` z@S8R}Hh&?e!S3@(Mg>H$+gMtMOPfHw;W${=!ORKWl4i|L$H;S^q&Ig-u+HF|C>4Je z?DV4xcDYF3Sn2r*qm~2biK#A9opvkNP2=IGpOG((WgRg^EC_b`(FMDGOtR_v*tK>X zpg28eI@W2oa=cE1oi;l*EZ5Bsc3zQ!UA}jnq_45zzp57;vp78+>=f-*UP(r<8}~D5 zvt#pe-PM7K^qr`4L$KRT*HHw!VL%(W9B>UY{TCGN*53^uet#A0TzbMr<+|m;uJ3#A zN5L+;c{V*i;hZ1`T#ipKR}}45es@N&8_zk~>{$O>|K|rg6UF2F^~8g{zP?`lrmpL@ zZHGU4dwcVU$lQ&iG=_cCG{dm>NEgo}ZQI`R0kv)GN%Ek<*>&B$CJK-A7J}V!zkdCC zx@ejv9h$>mm>Qs6*LlgY7k%HyxP4kGQbphQ)-zxA^XJc5A3@AN(|=^z;qHTT{+eM< zXp4!$vlZ;N5p!iaeEYs1MyHn?nY(>PuBs}kN7mvGT1KYt&h)R%`5e76S-D(S*Y$!i z@7W4=+mtarf6v9M&dwCOP8Bhv7cazPJ!8x-oo(XXxjcWz=V(32y`0bI_xJawJBgQL zRaKs?V3$!DBYt01m7OV$kt$-S-#%p<^UJonZY;&cU$f7}eRJ52)sWQbbn3d!vlZ;N zXeUpjTUzRvu20l;t$@KUhWh;JTLaC?TV3-XW_*tJxme7Os$f^*K5WdMb9P3$u2Zxl z^PwJTY{r0}^-rU1Tg`S~+7>vU&rkDIRi(`i1Ur_CW%Ppc6VafcXvZ<~$NYaabzOU6 z^S7nqaV=YIrbSb2b|Ba>VfOh6T}SM9C#CB;J1++a*1>K#n{XsTu-o_T195)BQ9*V+ z5xdp)U}s$Fx;~%Jn$tnBW3Z%2ISp-T*O8?R8R{;q#Sg|-IIGc6E7=puSA@20ou8nb<+zZc5CT{0J=20X5kcFs=#ENoqJLl{bT2zJ|y zX+5OaiUrIA2zJIgWXjF*0D@h)=O>z`aee|<*XAXdW+BAA z(12j)`~8-0KOuJ`hs@f}PfKuq_C9%?WlmKLM^Jm81c|4(BHT0Kv{TX@ZRy{=h8~fMB;h z=O?gw!Dcsl1`zDjGeGPBK(Nb6+qND8pb&y@jz+Lk&wzc*=1vF!2zI4(AUPNdXleum zJLe}rSsH!1fM91{h7~X1Fv7vQ&O;FFoSy&yJp%}K7Bpalm-l!bi3J2Z=O@4g8~}nH z&QHKRmM$JF=(QE>jCfGD13<8Iegbl8Hqd6bPXA(6RUQQ(*y%0@HEH01EIb4|=O+L_ zV+Mkq^AnKY@HU$(g#v<|+xg)*Z3fmY0D_&;G#qF3qS-)uF2<^dAlQ}CW@pfI;7Din zVtt5v77^@_^An)c4nVNO`3ab}Xc;zPt<(VoJ3zBPzIFot_7Uv%rfpm2C-ALe z00?$~HoHQfr_hW8f(UlbPr%ywqpoXPhX8_|^AoW93l6N>H7x;xoeO^8P$@(~8q_a5 z1i`MX5%2s2rgG+Qa9a}sf?bZ>9Zw-gzlA&i!LF>@hV3d2e176|I(ZbJet}@8EgE(_ zmA7r%b)826WvVaU-`_n7!LB%`)2VG+&!HS|WoVk_tw_Fh0+CLh+s4Pb`u}^$-t)S? zfB*jcV1ENU_wCy^4`CC}e8meFed6Pho>t3qQUw6OAY$Kcf?ay5GM#Vu$D4+{tb<+TxvNNL4g$F2H0wFQPCYK(Jw4+8Ripy|_@jT>wY!(5 z!T^rr{RLjuMNvjYiJEH)bs-rB#&E0xkrX|#ee)50u9Em#gKrCh^pk-6oT zl18X2IxJjswyb4IO|7=p*3RC}IR(@%*1mGglJ%yqia1 zM+o7smm=u^0001HU*Gwjn_1%HY`X<>9-e=H`M-r11^|$xSdNP@EFFxDjJvgipJb0l zByHx3o90@jrMcT~q?{ioeZerpFf7grARbXY$4_^O4?9#H06+=|M_8Xf^y>Y0`D1&@ zwSfY0S4OFN@d0(A?%InE51c3pE>Lk9y%C_pzr|ZVjbh32_cCL zJ3cT`b$}fLAQ>?X>kouKeD${VIgZ>I+#oh)R~dv=YPsf&{`qCuL&FP7;kIw;&}?s1 zi0&zi4qiB)Q?AQ9uHSOXAh|;ssn3%xe;^oP@#z4>ai7g(GDUS>V#1CPf~o`T5CF-D zVOf_q*l%@SQd^4ap5~qG$*nO6Dmzz+)cHrRTP?deuAbveoL}V{+w6Ip?zS!YWlgJZ z+M2$zIb;7_!C{rKRJ*QBOB$ZYbyoAcDW5-tQw50mzFk!3B_`~a{(~0-sk1{!45wSE z9%^FBMPrA6R7D5E12$)M%aE|TQ&?jZoY3VQdypw=UqKWL3!4PHs?5|5j=RdyLt&5d zr=w_kH0#ToHQz>;ovY1D%Q>{DK(c&WEX%sQp?<6LqGq_bt}j;{;|{U6p;w`|y&jwXO=E|El;ZKy zE#1TU)tbyQO;))&;}3iHAJpW1#{ry>JV{8NJV{8N=GQ}>1V}=DU=j($gpd#d5rF^( z5==yp2ovNtQBhY`6j!Yqbk&=>dDGo>t(@n%S+{!KdiHjkw6(2&xK%g1T~}vYtre?? zEfq*Z+dukWuRsfOpVxi=$lss4+`XT7KF>EBo>#9sN>&Vt-9HgzcQVuw!svVH&%Rjf znb^e0^!?)#*GljF$9Vgy;qHG`%`JLFkk3;3_=a^NdpGIbrK%iM2M(%h4y6YUmGwM# z=CA*Pl?DLjvTXNN$Xl>r2e)N{MJ3*`ja<1AJ@Q&s*B{K?XHA{6?jepE8lKIJJnw8D z9vpt--`7STPC^d>mV(KtsRJiQ(!%?R&|!J#P)gu{q-3wqu?;h}u+r-o%4({loGo6t zbKmON^#0h?%p=g$hh-D*Esxzc{{NEJmcNXGS`eX~E^@O{N-=F%XxSvn>y;Gr$%=N$ zR{qoy9{T4yAHbXenB98YS05H8*uk5yV6nM7K5_J=w;kOlwHr^&R}Y~I#(Ri^rP1Sm z8hHn52(ScPzj?=6|1`gN7jISk3~=*zu+41@Z6iY)L9`LNZUaN#n4np|tgP<*g`dTy zM`E#4vHKOVam)CqX>icYOUX?XxeHq5n!lN)8)df2WV{Ne#&*x6>`f;W_lIR5XKC3Y<)dLy@sar)8v(i!cR{NGBjaCTSwP5HuW9*@YX#T zH2^=@JE;ltk}ILE1B=QBx5l#Thj`xpFRO^%^W8(VqLz+*$6k)FoWrQWBkkSm9~%R^ znYJkE=;7IZgq7^X>y9Z~Pi1tU)%U!Z(f)#}X;{(lTcY+?Y~NF;qdhMiXnnj`U*JWw zYq;4hGWS-(`-H4ykF<23uy8kf#a6m;6QXaVW~`^GYmu33w_*F>iSY-Y!KeY4MYw`s z2f)`ZUb&X)>u2Znet$j0#{Oq-+`bQ^29LCf&nK(84>NN*81^pA-icT2A;bIALPJ`A zpSfzQtG3tI)EDX;sA>JNyShPdTgfBTG?tLU60-y(U#7uHqfnVGP&txyUV+iiF@>3i zTDl=Z)iqMo^@yexH8-7p`65)?0L*FA?mJ4DuQw;)KViY*ar5?QPR(vwPWSiML+ltj zISw~3-SYvN1Og%X8~Ox2sM`QFNkP*Zh%ds(o_R#=^B_oe6y zcnXJ5<-k*|$)quv)F;a{T%1ICILhI%F&-1cnLI+J&&ag96Ew~Cntky61Hjz#)dB=N zXzak^GCdh=J4iFPC(U;cLH$M#0XueJNxVG%aQM{ug0-7#f)?3S78tltxlA+<}p(hZjPT|{3Pqjc?u_6oP`K<2uf&G zLQj!>ZQIbRZ(f~-?+E}9>;Ra%Gv_Xmu71q1xinOgqMK_Ek%Zw)KB3ZSbk>4|v`voC zAoLJm$#`(@Uh~F|^h}2!BafTmlADS&%ky(wC95iG0&5#uJEJYF9hIy7PN&mgFi2$t zk1t>*VGJflXJHIBkIBKAm^g_mK~RE%%2`64NMR`~UVZWW->0YGVjut@*a0v<%R>)` zPyVGK(BkuDam8}3!YVSC&ANwR2y|4Gkf-3*yqwbY{eQb~8F~n?WPI|;o#seHtxPAR zQn5&ci^PPS%+0k`S5>aBU$;5h)78;d<*#%(9eSNsDiL!r4jrK)v_yoONKK5Ng!uUq zg^`3xB{EV@7I{2xz41EC7Jvl^c5pi}SgJn#=pQd0EN-n`A=SDVN;hfpe%Br%i96Fn z&^fXMW@-Ye&#{=E?lxRJ9i9Fh?&ShY$LQ#2Q$s_lLWv6>+ul(5WOH?%*5C}yWmycmw6y+cc*34576HQ{FomHNje;oRP9lL35A_xOGJ}*1j z?IzjH=4G?C&5K~Qh+3o;8;T9IREriv^%NCq5yXQ%dGsRW3&f)zrYBDktU`<6C87rp zt+nkY?%T}ln0|y35DD{Rm=ExOE)UQC_Pgf2#bhe8wz~RhXGgY`q<_EIAs|U3%JW># zqjNTXutHwl2Q!&t6uEBC{dLrL1I`2%ScOOhP#HWf5RD<_h%!h8W`M+su4sm7=59=7 zT95VC2EIOxV1k4c$3oVXMu)EJ`CN`tdh=4H=wz{B?X!gA*pKeio-AEms3z+ZXmhUk zeBu1o^5xg-bFJp$+h@1mH0v+!&Tcj4H`nH8E>6I)v3#Xs77NR>(<(tsfQ)I%wvsfA zPNxITP60!!)oSf_TMj__-(VQ?7;_k71(}4sY{0{Q`2KTuulv3GEqze54b$+v;H2jV zz9&RHrV?Eh1R~C%hD0F^uT)7jAH(qET7lSwEQ?X#b6{i>YDVUjMg~HtdSU8xy|dDa z0Dcf|tS>!%xROj@_NS+7T0}!02ApvKm=FM-!k7?9i0An!(+ZL5hkghY9QkD@YbMpa z?M#--vaKWyV{oejXQ#+;(x@WI@c`%$9Q^2dQN-)Fj4!)Eiik#CIXv=yyI(1Qlqz0` zBL)UY0vei@AW(tAGKG}_B8*Dosu$|Jdwr)AOqSGsgb>LhRhN0Ck-<5$t(2zX!G{py zactZ9pU2VmhmU{RyVsbet^f|;r!9R@3W$Xw5?U2h9w}P+;D>3Z8?TK=9P-eaWrL9L z2|{=n#s?T=7}y|zErh`sgFskX1|%@X&=n>tPkD%e0v7rZc?dFS2b9u#@7V!Eg5j}* zsch{2n%vy>-qUl={c!TnxjFZC$~xR!oP$CFO<6Py!ytqJQULnUsMxAep`LuuE*~3F zgnbltPUvXR{Q&?49b_^X0y`TjAZNBoqys?SiX|OdCTts=!dY1{KA$x@oM5oobhq=I z;Lzjm`P<#R;Slxli4OyLA9z^>dvQX&*dbo*;CDCyS6SD6*`M673=QP@`q}yUA9Hm# zyWnBTvo#%4tfxbQphCJeIP4@6iKnM$Y-}usVPJRY= zG;?bc7R$txLpNj7*=(l8(XoknvE=9RS$)Ni&EMqZ&aBXAbW>Cu!!Z+pb1B+CC*X@sLBRtP_7je&t z(DtaC(DLNzBL<3neH$ycoah?2Uj;{)3_|3_AuwDwN8mIYCRYglgO-<@)7;!#dU`r)yg(pub#)C74`07Tds3^_F)=add}n9p7e#t|d%+ul ztuEW5z{a92ujnZ_PGPW&rd-wFzz-!7^_a%i8Z%(CfNlXWBP1{a$Kjd*n3=E9%{%Tk zi%Z2!CT>Q5j7kEwEyRraRU?f?74Xbyn4TPkNThx1?Cb>l@@n1{ORkC~{X0E-kkgUv z(Dmg}U8DW7)aglcL|*F6JBimWUO0QfO(3rigClRjheG&J1p7tn+dJ> z;Ns$fzE)RP^EYj8gG?r?sHi~QhnpH28bl%ye?vk-!rxwpnwpxdtgPth=(xDJjEsz; zq9VCm4tFv>K3-g0tWv4qCN#6?6<>Q>lo1uQ)LyzW_>*pavMejvS6~rxj!}{BF{An&*G*w6>DrU~YnuB; zrIgSro=vM$Ze4xXo*C`My8FHV^rUj-vCPm_A<2yPzUb0glBQD)kwXxP^bf<;K32t& z!H#s8Ee+*7`j$WNHCJ12rIiIPDy#gjo<8ejXLp?I#A8cKqTs}BqT2=by2Bn3>^cjT zK?BWqJcN!OqLXYh(~Gv0k$V|S!>Kb88`+^wzJbyrskxk3nE zI0A$K9*Zs@ily!!VikqQ%IYGBC?JT%E=w(l1Qq2F1T;Yi34zHCxg%F@l1wHC2{{Q# z$Rs3_YZ5X!C&!%MKFn12ewk(lElq-3->;@p|9LDineLmY-hP zTzcBjt}-;_>uayT>hQ2UQJA`GRYjLleLpCOS=V;+vbH);TNKru9h`US(@4)%$~f=# z=30IU;&8awaX7yux@w=gu;~?kquXOgn@is@Xj8YYdFj#LJurRD1D~%Ts+{9g6J)+R zm*#s=h+XznTDPT5mHftw^GE&mfv2YY=G!$yb0fgq{9Q}*xZHye=N@(HFaL^;5a?7x z)I=nRaTjWTc#cJ7Q7EJq4jw#6B)sy?h0U`6N!H(Ir7)gUbsXlQ640%9HpP`AaZxoWi-E=Pr-MN`o82A)icnJ~ zlM#WCkPte=7>ve-%;<(7&#Kcq)JZZ_9x)ami4 zPQerFgR1jQ^;tTlTvwUeTNKfAEnFj))rIY84*s+u!LO^conL}DoS(CZ9Tz(e#}-{( zWOK}%+JFhDrJ6G5&_>Vd+@s!`7Rt7I)TMlAY&}bBHqfhcXx`&Ae?CQYSNd0~IhB9u zvv1y?AGO<~C2O0x+1s4I_;&1=>nFzCIOEn|<3~pV85xN;vUO6(6SV-DAOeC~+0~Ks^mHnK0x=Ol-IEHS zZ;uGT0@?DYRI|{aM(+j@fGI*xe`LOevbBxE;=mOO1={;?LWl(zGaU?0EU|$6($mwE z*dY9hG9b1jfN|5lzCJeHl!{$ZQ4tYdiR~>rk0*L%l2Mtg!*5ID@RsiU@Rk&x zD*x>@r?%Cmh2sy+FF_p6uTty)7dy`ViJ>L4`G+S}0TWuHoph)fT;zmy4loQ@7mpU{P=Mq@a30ZQVTE=B7m-cIv9K% z5!kR{gS24|puEV&du(?a9gI!zh7YlKcLXB9=4V^BY>~_5L?AFQkTPJXf>lPFjyl#3 z;lXz;#fnd?X((#PVw|~g<3@X*cIM0(%7Ew*N{8LHIXW@O1o(rBe-yOV5g!WyR&x~&hb?QxVHD*jR zWlp6iycW)*aJU?Inm30QJV9^1L95r&qD3@s9?kWjXtpb`)MvA%ndMXaQl>SYbFPz( zYmQ%P8fY4kX$isr)v#%#195S2L_nN}5ETSL8nusD;l=i-liE1PzJ2>B1L~640jZ9F zx88b-07ipQh!j7Zl};8mHN^JR4l0$307(XEjfGaMSV1i8-@l(NUc(y2LqqgvubK?d zo=6#BfdmXb9BbZ*C{EOCn^6)`_=MBw2#mZ*3*yN3@I`ElicoWS+BbXK)%juH)`flE zmg?J;cdn-}s{eW%y_H0jiH&)YMR8s!UvIp!f1UjBro_D;?w;Em#r`T6+~3mA80>%R_yKukZ_>>F!2(k{l9mKJKExVTt6GgJU| z=bt+6fRSW!FA&7iVdQ#tnlfOg`jS^?RuDovQ^lq71pjY7*}Qqp+iU;++M7#Xcx}mF zmOuN{tIsc5`RcNb@4mg~Ums>hdYh}_drM+az->+Otq$_6NDSqd9S(;lb{x)7p=*{0 zZJm~TWNgQUNxIbOa68DHDF^C?fr)uKg}YKV0l&@nnpl2vOke6udTlO+*g^ZUW}6l6 z*uLx1!~*Yeej6sXlmyd}fUTB#d3jL=sZ=2$6DxY`su>Qt_tvagBON}#7z7cBiHV^W zkhih%5^HH_ToaQmtiXcGqm3A0&+%PU0*uPR=!3MVDm5(t0TQdUQ~+y;uq9>|=|nBS z)B+okvS6hZHV9-3xY7nYdrPR;t3mtw`(yCMrX-CF5?z3Fcxi9fTo3%bKH@7)n!m0z zIwR!J!ud}+O`1B}ea@n%7cP13`NhvXw`k$wCC@&OpY!HCKE-v0o7>EQgWL6Gv6`&F zrt?Rt0(RF|m-EXGhr`8=!x<{{s_3wPPLA95h&pmYZxYd^xgnM_DLl+sbIrMPF%sLI zFs0mgOv$lFZ^yZqvuC3?3$Zijdcf)o88c9aZH*ltx8tGjS2<~lPtuVfK0clRW>`Tq zDl9ULh1m!LHWq89Hnt}PUcycWuzmXMWGI{?FE5XkA%s=dNehvqM~~Wc(V=*XU4`*L zbYPj(4#?P;H`)aYg%Aex)!7^f5fDcf(0(Ufv<0V*)l(pyKZQ{dhC_!A(ZN6;Z7#St zQnr4IeDR*sFhl@}thKKZOEKeg;m|Z1%}A}Mb2B}zM)rw1;d^~iM9_h)Zj+r}c*1?z zqQ@R{8BhN7pxf9-W{e-@I_eMj`SR0qmo9jGmdk{l>)+HBM4*6M8@jtJ;(I-xnc#4^ z*l{>RM{C}`@Q+6aeeh73&*-j8F8x=g>oaB;<+Ch$=T3_}XxUs^fDwwBv}CsB`CR0A zrp!B5r@Jyko8VI6Ki2o1-}|n9q%mVR9bUvy2Ah^Im=+>22m}$3Y7N-Dc{35f8pZ5< z0Mr3NAe$1w@a&a1d`F({&i2xf{q^Brv|TyXlT2ChRSz*ijK zhytJQpp1wp5rYe2=!k&{A|T%BfknC|AI2aY2qLzm7O)v{2(DdX#g-LTNSK|W3fsCf zG?Bdx29e<4&xFBY2Ug93a1_z(u!VMww<;=6?`(`Z(4OW$P;jolB&x3{N>>=sogG@A z;8T0)M28|!UlfTK`--CniX*!+WestMtAjqR%Z#4|8GHh*ocnB!H@(ZCE^g|OjuS|r_Bd;*YuM zf{%}nxG)=*rbDq3$^}zovo3L6Whx6MG#S$v^`Ne3?Y6O);cBv z7)rHv(P6UH>TP#56+~|0X~D+qf-gdD=LpQziXXzxfDV-%jzZkNLXt%qMlJ zekLO?!g4rV>^PjE$6{k9vvELMeWAi{`t7)hXtsm5;W2R8MHB8)eTtNva8U)g_Fs0@ zr%s3VX_Lt9*ooCaQ`?m>CPS~;WH6hJbfm$}A<)H-7BMWki-7<%-J@EH(%zs5N&-?S z)GnpM0h2(;BzTEkP}w8`_|F{X;hqe&@0UmNU+K??O{FO+4mxE;nL{`Zi}T2uX+FM6rYeo-B`QPYAAgvrpmp7_a~v6D366WY!>b;dYp>bPKs0;p1{#Lesx{{5uY(bQ@+8pZHldN1HZ!Zd~>u&goaq-@XyLqccU z&(;wm`1fOXy;{*r}rQ$2i7qKMG@K-sT zO6335o#m}}7gjx3-THWA@3XBFpY50oL{GO&c(AVX-qMD9^DD2n8Kw?zgh^>B^Z zyF2Ut4+XGp-8zipLIxNB8!Ny?dzh`0)YMeq9*Zkiu8gKbv<4z@*cEf24!BJiI4-+q z&mQ3T;s9M__iyv2%`>YquT9OqKCAfp+_D?KqtXzuJke@EkR z6pVs}on=s5P0;Tb2`ugs++BkP_uv*>f(C*FOIQe&po`1m?h@Qx7k9VdPH=a*o9DfC z>-~05)$WI#Q!_n1Gv{no|N7sPK`TteZxB#@ynnd-kX?wkfnpIUz`JPy+~ivgIA6T- zw{z)n=dFMl>3dJlqY9E$!-6RH5!90v)5nFdy+)DRjY`KrBy*Mr>Sk~-$mkZsQ>W!87{0fF=VrKR=i4`Jg z%l!pYN4Z{VU~Qma`{UA>=jdua8<$o4E~*E2C)#1D^1>Tq(v6`oZD0BQY>ay`?Te)+ z^!_Bz&KQRo8RoFU?UadcgXmD!F7vFJNlZS%a`RT;TlS~pbuK_bo8X~{RK%Z&A z-cKwG@Z2MrnMv+DUrev+{z^^9h%GHe_3M|gy+&d>2T>E7E=nRDk~#;P){J@_8L&a( zS2UtL4N)&5go+x4Mw#6(jIG@8(7}*%VR$O%2o)GwM`ean?3ZPC6FePfB{Z!l?8kHJ zI0oiP9!yKVeRg`e^vt{zk@PHk)e3(SEt4uac{w{|PXB~u03)V4Br2sHK7lHwjmTjP zjqC8yGOVIs5NTxQ3Zs2)%|Y(;dSxC0h#MDHC*?gg@3N({g8jE_5^oL5h~+B*iB}sg zCAEki4Z1FcGYY~z_V$#enX=<1r?2iuFaBUVJ(4fP>?|?z;S7tU26+C*NUefuG1xjz z@>^9(+xWU+g=dv;mEORkYT8U}#{L@Q(y-IUD)Ffqp!FJSriih?i^C`^=;J-xwP;xG zhbotqLmCjbwXMED&4pJZirgZ-UTfHVJzkea%h<=r(6tE<5a%)RHAMz^abjRZtQ3De zE+Ak^e``M6Ndu>R%9h`G8&kr9HyNk3VP+jP*^FdnhmetBWI_|__$ME$px+#3)t35LC(DFON#*;+bl z*8^pM7uKJL8h)g`E!rXH$yt7UZp`*=z1sof0!I033TXCj_w}s@BGwCa>i*_l$gE7v zm`1_c_bcw~?PiQRI081_^Npl-jVsbjDP;)?G2Wk?YTU5$lUe^3s2v`Lzquo6KBv=b zy-@E&FP(IQGzg-_l~IGzX)h8)d$da+%#3sI-*C#i!Czi0fG1TGTZ};#qx-y=$j0t? zEF|=KwK3BAc@hkp)ckVV&4s)_+I}R_YhqzWmHnucmMAcI%YfoXquW`m;-ulqIw#!i zwUnwSo}iu7Nmo5dC-<4KM5=da5PCj8`=Ih9*>VLURqZ&9i2?Oaepn&X|9F@nN8Tf% zA~JEDo#qxlCXHf?)IcHjCqaAAtn)X&?-9l)?4wR3z#KuM zIHjef?jm^G^a=Bi{rIOF?9Z&C0fxC56Gg9IdlN5MQp)iJ<_`tS>nu=0gLCxv2q>WM z^zKsBs{WD&CcyG8v%GKvQ}vkIoDb_?@-1d0dV&WPoQ~eOn-vtewz!0G!zJ(f{3zIP z?HDg&jyb!Fz@V}D%x2`v*{C>jQg^ZFxJekeqQ6({A@$K`(WB^{BHt!WM^xl`ibvlD zgurrx$*K^hQ7ioImGfgWbgcZ#(!zm>V+H^9X!hrMb`C$Rf3smlZK7wm}c zwS$S;jkevGwqIhi4^v20O-U~hV!2bEoJLQ*&VOc!=NA1Oi+wp#4c3zoK@@sv0#EoH zQoD@40P(tASmPOS7SAtQz)bOqxo{>y#ln?;e`((}+tJbvK4}`E&$B6;ckQ2fmPo3j zp!M#Em^sX}8x2p`#yLi+d|?(d2tYB--TJ&8Dp^r{IF_$l%Iw7y9#%Lfk)5v-=h2|G zxC3_{`}ov<={GL6a{M#vi?n5`Lghp}HF485fu7AjF4mx&2%?u(&s%Z%q#21=R|%iYLf60@6WKEJc7VWIQ~4Oq+B}=HrAI zc9b1x@0(SqfXd}9;ztYax?hd4j#;zR4H3dK&58ym6EtW1UKyUgVFoEAMtd2;l*F2ucWX8=-iHE8vB^E4%4`EwMR z(nAP(pf}`?k1h`g8CG{N1Z@lVZA6|WJLG`uqwqwyn>p%Gfn(ueBH{!1k?7Q8zHW-T z7?*s_hQfgP{oJ*?4N56Lpz?)LjaZyCZV_&7^!<_fDVXxKxRm<>8}sbLxM)E4ttjG< zfajugtKD0@V0YkSBLRiqEdmA9uUJy{jIU5kZ;9VHIREhQ5P@A4P$K7cs|qggu(M?j z1)=8*f88>Swg+3cJDS`Dew!t~{qy$2D%0=v%Lp6BbK#u;84PL#|J?Z{59@XKML`b) z>Z(~W1!?6uT*x03=P7*5%aO%a*gn$L#A{&I4`2LE&p_D##OajkJz6Hy`wB31rQ|A= zY{BccVT*>nR-jiiBWr4|J%l0X6!5RK6q^gESkyc>*jqNNB8Uh%{L(1tbNDdfkeyXF zcVoCuWX+<6==;Bs5!^wsMMhoF?ffQ$VI0{d|7F_+?( ze6Qm1p31QrLcCksJmHv8rl{xRdB|k%*4C*K-f;e4d`4K!M=X<>lfjxlXOx4*>0KNF z>tk<(dZMmR(Z)|R+ppKp0*@)oPZqXMM0@1@DRN+Hx|1?k=l9bx)nGvv2dnL|k-lBE z5)6qYYPVm0{9*h3vOw%V~+2t zw*2|*I=H8X>hGeJfYfVWYrw{X6a!j&ju*;mYV%@yXJ<~wi4!7CBS;;%h%Fo~v3+6l zTtSfZGET_-{g2e3-^9LLeJiaT-K@WFq$}}`ei@r|Y$*{T3U@_{x~FpxyTSlg_cncdGc(YmChL}O)QB6=3~tZO-Oz!B*IWXXJ} zM1Wz|hs1Vvb~k2S5?C_xN2`G`^3wE#FniQP-cV)&!t+e`EX)DxAuu{SDg>^a21a+o`UWeqxw8uX zGQGuW2s^p;{DAO}d1}oi4yW@=S7d`(nf;%YFlZl@YN4zg*A5H^U@Arf*{Lg@PieW+ z!$?DU8W@oS1Qqqb!ysx!Hm+%<8n4?oux|cmY=B7y_K}wPKZpE#b;RrGRUCs3xA6WO z`ybr|`~2>KH*l{!S;4871AZG#Q#kU z)}hD`Sfy*2ZFRVIiCD1v78em}m(LEU{+nM;v*{j+?|&14jS;U*xj+IVwBwNfx4nlS zzg+@>pfG0&r_xHhjgoId>7b8fr0=6Fa=WLVjSdL5)OU=!T*~$);5u4(wS^{QDMR2w zu&_UFGb=Jbn~1xX|J;U_rPX8;+te)PnN^Yf_VeBRHfGG6uNf+R{>}u0);N}Da%Nfm z9Rd z_CbW9XAZlMl6f6IF8%rYwoD^!=X+yYGIhK)Ns<(2<&WdbIHy)qAg1MCeE72$HA7{x zNG&h=v@`+yEJT{(tFi$W=8f%(@7HE)v$?`O>F+Z<_uVR>!q`tN?pS*@?r))Ub+H7_ zJEiDC^}Z{X`^7pDC8#8q;*9`C@baUxqOBd1Da_-xVAE19vyAB#hAaDvg8OWpMzli*}zeyTQH}3 zAP8MsFe5^R*66z5wXO86xi2CN#*>pJvZI4Ur&gbg$cY<5vkf>b&On9xQ(aSVI5(;j zEph&5fG%kFQuWa8*B??q2rF%^^=@xlCf~+Zb;m&o$kMgTjR{krD*#)B)#+;VuO&<= z7`Q8gO`*E6__O$-;$@dp>;-WO@v?Qi<7&A5z#3K82xE2(if+i4ZQ~|iff)s)g(U}R ziNtG17N+_|!Uuq*-JyeOb4HlkbSokU4t6E4qi?^L3Ui=o^JNS6%cBB`6`Zwn&PjyR zfY=li8zEHUahfA$lBoOTrQgJ9WqSN=DLL4a`f|R(A%^Ec6-dL$<)h9de7L})rbJIx z=64}fTs~|nprqO?=0I86@VreD1so|v1w)RD3pm(;!Ad+^zqodo^2TYLwbd@smepuI zI(Jwv3BLO>j;>5PS&pDUp`f8?c0?s&gHLfqP~Bk4I9yGqaPPJ1Ovb6?r3Fe*E{owe z5FYvyfxT7tjSpRZsUixA488tPA{;hV#PIP24Ml2^f3|Nh*=b6U6f?}bb{B+Pfk7?D z^axA!qFr98$aM3)&j*joX}0B7JQ?cjAfl^RFRuHDSD7dvYA(p;TZJx|fU;Dy*hW`__^18R5XF3&`#8mXVBp8x$+OLZ zPZdgH)ofvp5aj~szk30ObE{{juPqzsi7W)euszP#_ zgJcRQ+@Q2S2pb{3_^4PZs2`>Y`t)iR*%F7-Rr~RjtMr~mSMOVyaDtp$JGRTFPo|=9(S*|}*} z7;=;xFF0t+PLKF^l`AmD!Fw-$?Is;-KQjB>u)DU-nH)W6LS$%s_3=HVw3nboQQGQ z2*db^7GIZzQz=@~V9|KD%WsZChQ$j%)N%1#`caI%zv}LG{s~Df=^$#G^JN`g^BM3I zZ`C(LcJtQ4L(^ln?>U#uYai|8qY%{sMqY(Ie^_0>mY~e~4+_>?gEE>BsqA0+Tc8%4uDwgM zy5B!vRO{V-lDDNVYU%8+^mSLml4gj;Fmftr=S`+qDQ-}WJ^dR(G4PK}3E>SiU$f`~ zfw=@no$kb0CR@IyAINji^EoB4D-)gqHA8!8AVssbNqm@K1RRCMUfi757hTzil!Fm{rsi2hghIhbE<6`2m}BjxE%!TH2$L^Cm>{!?)l>yz!{|p5V`3fX-4i?X=<7b(X_L z7%DGW6^###&uHMZR{nlE1_B9zBhJ0t%Mnd5x4g$Ly)*aTj|)TS%s#YXYe8)5A#v`b z3R{8XZ!(Z3ntj0zca!W#(N7s^X;+WWpqBJm=+^n!tCg#(c0Xd{cOmvCU0O4G^wZ!_ ztM=db^Vg>J#JU-!&mIX&Wp8y)@-DQWN`x!DR?`K4xYd7MmwlYDE3L3Hx$GFmM@MN1 zg+_W%7twt-S17*!p8=_bw3#-`ySI%TA{>R25v=jcI*wb#+C>nITVCUte#jzl($u zA?L~)(!b?gU|O@-h`O%~J&WnSBoON1Nk$j&9UWDn{_RLC&@ZO7c+en4xuNs<=8C5p zNHC^R9-sMPrI_=6>joOsiw9OC|HC{ll2GO_Pf@6_fKt@{wTQ@1QL`W}FU4d*V;u)R z>UXWrI<;A$^g@^a%)2$$bkTKh2;BynaN%?a`N_3Me}DgZ;z6!AEQZ@;1-p{q9lq?q z2lKiUR4N8YQ9JN1+Rx=(wT}D%w;VWha7K9hscF|QG#CF|+?!Wxp>Zn8ojVD)7)=3f zm?&fDR(df z$I9vICia@YB_m5qO`UFF{MJN<$)hZB9HxQpGtK>nh%|}W6e}!|cH=%-D5+)~Ll#08 z>$kk-ecv;odA+hK+=8U5S%We%Jw$%VX~^U$_ns|YA58ma^;bWR1n+)BI_Y^F1yZ-1#3)d-O0VdUr<dmTqE?T2r#FjXxs=HCNhPEqhr*(!p1n*zdcxLH&XgJ? zNIKT45yht?GX3UFPq*2QWpeoy_gX)aiKY!YooRET=ikr%KabMK9kpfg`(I=At6VJf zNCfu#B9IX6&25#naFtiq{Sa|A^EGJbG4e1>Kl=fMj|uc$<8!>(gf*ia`E z28FwEN6k-8R#jJP-a+sYN75_Nd>LChEx7pj;#-~0C^fP=@dAtu%&ZANxR8#sK(?4* z^0Fl20C3Wbl5g-kRmv!}L6iy8p5!8k4Doop!O!)AmAO&&au(=h?uYA-vYwtT7XXF7 z`};-ZNLVjFYP)EYf7Jgzjir25UMK43P9xR#+Pm~TE1Ht$Jc7oXOQ5z)plQ@V@Soll zcgh(Xlb3v`Qq*iB0?906yT8K_G4=3hwx>>-x3{xv@w$I^EO~d&f&{V>Lr}v8@!^3k zL>>wX7%~a%tI7MH66yu^PfzCwxoYDm&)O`T>wdoaeD&qZH9wPQ7?C(TDzB%j``D?r z#Lmuc<5(&Zj>i~PRpF@kF03R%6?6PHoP%ri=-zcWhH~msDZ%_l9n}#F%0KH;A^5F2 z@m)&iep1-YiTCS7YlVl zc|nC_!S{YZvUF9dV8?}W(K1kT@ zL<`2BITX*Dhiu7ku{i*fBRQ;F$LIL$o8-&PaW9J>!#wkj2b?dn^H6Oaooe-R_U%S45%p8W34and^fOr+ zL(kuyf3!Y9GTUQ5x^vwR94M!wU&E~E*`-<|N$+pAG#bxI$QwDyCqrq$cCSVgNeW{@ ziaI!w5>UwQ>hecM!*=(pR9bVHTwX4vi6;JnX31&QcK}>jSz+nh+|2RrOqJ$XOtcWt zj|va}0+&RC$C%(q;CnRo{icXm>*vsPDl!#x!*XP7%+{YR)l=}jJeqnk!Q>N6zB#5-RftS`Ah4}|T^7#juD&6f=NW1Mf5NU_SX$)QZJ|1d3`Eo2fxpLZ-w=0Jus-qj?gB7XMkG0a;_>2Ghf zy}!P`gHMBO)uC-IH3wnz+Pk!UH|@OaHhJ&Dp6$seFNBc$U7Ay-fS#EQmB62!w4FqE@7VGSiL?6r!Li8x-6>5J1{0}Kj!hO=A0^%e%*MuJ{GhGf5gx$wEr^*kN_{n=<=QjdQbIRya(ZUxFU&f=$2FqEn9q6HQQ( zqFg(7o*Pf&$_)$+nK!X(si<55_9*<%T{6T&(+qZedl~eLs{qjPA8nTStJmJ~4$g=1 zSCvu!!6yT9(cGnROh<>dduYFQbeM45i-E`8Lc9pDMG?vomsgFnIrX0XVj^2Qk;8bC z#E6Ei8bS#?>KnI+n92gQVc!#hsLy7o&a#eSbblHp&QfmLOh4HZLjJttItp|QD7-54 zxg@%j>s{txFCGhRt&8S2YBPDRJnCS=^-Kv(2;LN(SO6i7u!y|S7X5_mFSUxUSR+(M zlx+t30a<`LGvKxkFfNUTKl~;;j_d6TT>>hW4n3cAKRcxlcReVPzYbl8po&E3 z^bB@v?y>uUE$Wb=X1ZS6k0BShR>mxv3ZeH2!vB0e3Xf&fOyJv~&l-dLB}98 zG4QJRHeY}szjDD(MumjFdR1aF3~gznHv zM1RzI#96=^G?twH{_zn<3dnZEahU=8SO1ui9QN&+gFoC@)Z&BEkgby9P;<&fQLx~& zuYT}%V&U@)Y>I&Xj5B#I&{*z)??6VNL}*0XLRHaY?K+`Sz*>%V%J?2K5>il5P!E*G zQlZtUI*4uX{c>E?5)C$c^BH#pREsk?87{kbcZmD9L%8g zVfvjn4b1e~rvd*;llR1$N!uHxF#ny0+CBDTW*7rkH4i7m^CQW{hc`V=!g9w?#V8at zUQ$^tj#9WlwRz@W6n<;j4&0tju_5+hZb-WS8Cw|CENmpR*i$3AV%h5F+76d#$7A|ju^?e_WBpC!Vd=J!{ei82^CaZm5(K&c2lcO#HXyS+=-Vu)kis9> zEr+`Otk^}S+?KOlfr%}cJj&^2Ut|q&Q*uR#_1GC1}ALYE}cvNQL3O z0vIVax)y$aFYn5ViZx1R{TYXq&0TRq?#(G)&s?(fe19}>pPK*qRsT$$+lu;&0(~Am zJtu!Sc_XbzIFEqpE=n)Cl}%H#(p6A(4w>>x>)myTcXy(!VD_mg7wDfyLp$j6`?`sR zos8UPlsYi*gsr^Ote-BREy$|LEag~_FRel*m@R?pi2FQf*sy)dl93RX4}T>nggA&p zk>Nq-8hHFsxUzZ^D%ATJ{M(R`H*pl~%uDRdO02E@lhj}0&$y6g zvZ{-!Z?eC4Xg2b`zI2&44h=cq|xHn=4~biVt2%TJ_^Re z&R_@ti3_6O6M3IKpw7!&OR9iIAdVIU7$~1TO!CLucsfGNjwol7P?9QKO1aP#P4o zL7fkGe+wn8V!2Th#x$FiwHOBJ<8%1Vz7<5XFw~#~MTV=}zbSi2I`7}hIFF*<6T`)!cA@S6x>5+0D}OeR%sgBeagf&B%FzR#PxInE{`h1S67LF*GI4*)p1 zhf7QJI0M7U0RRbu)R3l`%}=pfl%$lX7Rdh;QHWu|5CGs`D*#{$3zfjG|J~3D3yb`J d8>n1G5czw(J%v_GnErL3Agd}j(e< literal 45271 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 z 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 JD`gh){{XLH>7oDt From 1f97790ecbf7f96f7536d8c0be687e06f3e32022 Mon Sep 17 00:00:00 2001 From: Kelly-Ann Dolor Date: Tue, 29 Jul 2025 11:25:43 -0700 Subject: [PATCH 26/31] fixing yaml spacing --- mkdocs.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mkdocs.yml b/mkdocs.yml index c44bf74fded..38df860a43e 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -231,8 +231,8 @@ markdown_extensions: - pymdownx.superfences: custom_fences: - name: mermaid - class: mermaid - format: !!python/name:pymdownx.superfences.fence_code_format + class: mermaid + format: !!python/name:pymdownx.superfences.fence_code_format - pymdownx.tabbed: alternate_style: true - pymdownx.tasklist: From 51d1bf8548e8c782e0e8b412aa6d84433e05e8fc Mon Sep 17 00:00:00 2001 From: Kelly-Ann Dolor Date: Tue, 29 Jul 2025 12:04:26 -0700 Subject: [PATCH 27/31] Revert "[GH-2127] Add Shapely and WKT geometry support to STAC reader (#2128)" This reverts commit 5e7559a7ef548a0eeb73649e849d14335b988c55. --- python/tests/stac/test_collection_client.py | 125 -------------------- 1 file changed, 125 deletions(-) diff --git a/python/tests/stac/test_collection_client.py b/python/tests/stac/test_collection_client.py index 9c69406d291..1144e99005b 100644 --- a/python/tests/stac/test_collection_client.py +++ b/python/tests/stac/test_collection_client.py @@ -291,131 +291,6 @@ def test_save_to_geoparquet_with_geometry(self) -> None: # Check if the file was created assert os.path.exists(output_path), "GeoParquet file was not created" - def test_get_items_with_wkt_geometry(self) -> None: - """Test that WKT geometry strings are properly handled for spatial filtering.""" - client = Client.open(STAC_URLS["PLANETARY-COMPUTER"]) - collection = client.get_collection("aster-l1t") - - # Test with WKT polygon geometry - wkt_polygon = "POLYGON((90 -73, 105 -73, 105 -69, 90 -69, 90 -73))" - items_with_wkt = list(collection.get_items(geometry=wkt_polygon)) - - # Both should return similar number of items (may not be exactly same due to geometry differences) - assert items_with_wkt is not None - assert len(items_with_wkt) > 0 - - def test_get_dataframe_with_shapely_geometry(self) -> None: - """Test that Shapely geometry objects are properly handled for spatial filtering.""" - from shapely.geometry import Polygon - - client = Client.open(STAC_URLS["PLANETARY-COMPUTER"]) - collection = client.get_collection("aster-l1t") - - # Test with Shapely polygon geometry - shapely_polygon = Polygon( - [(90, -73), (105, -73), (105, -69), (90, -69), (90, -73)] - ) - df_with_shapely = collection.get_dataframe(geometry=shapely_polygon) - - # Both should return similar number of items - assert df_with_shapely is not None - assert df_with_shapely.count() > 0 - - def test_get_items_with_geometry_list(self) -> None: - """Test that lists of geometry objects are properly handled.""" - from shapely.geometry import Polygon - - client = Client.open(STAC_URLS["PLANETARY-COMPUTER"]) - collection = client.get_collection("aster-l1t") - - # Test with list of geometries (both WKT and Shapely) - wkt_polygon = "POLYGON((90 -73, 105 -73, 105 -69, 90 -69, 90 -73))" - shapely_polygon = Polygon( - [(-100, -72), (-90, -72), (-90, -62), (-100, -62), (-100, -72)] - ) - geometry_list = [wkt_polygon, shapely_polygon] - - items_with_geom_list = list(collection.get_items(geometry=geometry_list)) - - # Should return items from both geometries - assert items_with_geom_list is not None - assert len(items_with_geom_list) > 0 - - def test_geometry_takes_precedence_over_bbox(self) -> None: - """Test that geometry parameter takes precedence over bbox when both are provided.""" - from shapely.geometry import Polygon - - client = Client.open(STAC_URLS["PLANETARY-COMPUTER"]) - collection = client.get_collection("aster-l1t") - - # Define different spatial extents - bbox = [-180.0, -90.0, 180.0, 90.0] # World bbox - small_polygon = Polygon( - [(90, -73), (105, -73), (105, -69), (90, -69), (90, -73)] - ) # Small area - - # When both are provided, geometry should take precedence - items_with_both = list(collection.get_items(bbox=bbox, geometry=small_polygon)) - items_with_geom_only = list(collection.get_items(geometry=small_polygon)) - - # Results should be identical since geometry takes precedence - assert items_with_both is not None - assert items_with_geom_only is not None - assert len(items_with_both) == len(items_with_geom_only) - assert len(items_with_both) > 0 - - def test_get_dataframe_with_geometry_and_datetime(self) -> None: - """Test that geometry and datetime filters work together.""" - from shapely.geometry import Polygon - - client = Client.open(STAC_URLS["PLANETARY-COMPUTER"]) - collection = client.get_collection("aster-l1t") - - # Define spatial and temporal filters - polygon = Polygon([(90, -73), (105, -73), (105, -69), (90, -69), (90, -73)]) - datetime_range = ["2006-12-01T00:00:00Z", "2006-12-27T03:00:00Z"] - - df_with_both = collection.get_dataframe( - geometry=polygon, datetime=datetime_range - ) - df_with_geom_only = collection.get_dataframe(geometry=polygon) - - # Combined filter should return fewer or equal items than geometry-only filter - assert df_with_both is not None - assert df_with_geom_only is not None - assert df_with_both.count() <= df_with_geom_only.count() - - def test_save_to_geoparquet_with_geometry(self) -> None: - """Test saving to GeoParquet with geometry parameter.""" - from shapely.geometry import Polygon - import tempfile - import os - - client = Client.open(STAC_URLS["PLANETARY-COMPUTER"]) - collection = client.get_collection("aster-l1t") - - # Create a temporary directory for the output path and clean it up after the test - with tempfile.TemporaryDirectory() as tmpdirname: - output_path = f"{tmpdirname}/test_geometry_geoparquet_output" - - # Define spatial and temporal extents - polygon = Polygon( - [(-180, -90), (180, -90), (180, 90), (-180, 90), (-180, -90)] - ) - datetime_range = [["2006-01-01T00:00:00Z", "2007-01-01T00:00:00Z"]] - - # Call the method to save the DataFrame to GeoParquet - collection.save_to_geoparquet( - output_path=output_path, geometry=polygon, datetime=datetime_range - ) - - # Check if the file was created - assert os.path.exists(output_path), "GeoParquet file was not created" - - # Optionally, you can load the file back and check its contents - df_loaded = collection.spark.read.format("geoparquet").load(output_path) - assert df_loaded.count() > 0, "Loaded GeoParquet file is empty" - def test_get_items_with_tuple_datetime(self) -> None: """Test that tuples are properly handled as datetime input (same as lists).""" client = Client.open(STAC_URLS["PLANETARY-COMPUTER"]) From 527f8c9af9ad2ebaaa8712d8d09b781349e452ba Mon Sep 17 00:00:00 2001 From: Kelly-Ann Dolor Date: Tue, 29 Jul 2025 13:20:14 -0700 Subject: [PATCH 28/31] changing date to comply --- docs/blog/posts/spatial-tables-data-lakehouses.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/blog/posts/spatial-tables-data-lakehouses.md b/docs/blog/posts/spatial-tables-data-lakehouses.md index e4fd683d758..02be5bfb94a 100644 --- a/docs/blog/posts/spatial-tables-data-lakehouses.md +++ b/docs/blog/posts/spatial-tables-data-lakehouses.md @@ -1,6 +1,6 @@ --- date: - created: 2025-7-26 + created: 2025-08-01 authors: - matt_powers From 057a4eb442987812b834f48f2b007cb56b7af5be Mon Sep 17 00:00:00 2001 From: Kelly-Ann Dolor Date: Wed, 30 Jul 2025 13:43:08 -0700 Subject: [PATCH 29/31] fixing typos etc for review --- .../posts/spatial-tables-data-lakehouses.md | 42 +++++++++---------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/docs/blog/posts/spatial-tables-data-lakehouses.md b/docs/blog/posts/spatial-tables-data-lakehouses.md index 02be5bfb94a..c4e0a53e85b 100644 --- a/docs/blog/posts/spatial-tables-data-lakehouses.md +++ b/docs/blog/posts/spatial-tables-data-lakehouses.md @@ -45,7 +45,7 @@ distinct versions ideal for historical analysis and auditing how geometries or a * **Time travel:** Versioning lets you query spatial data (including boundaries and specific locations) exactly as that data existed at a specific time in the past, crucial for historical spatial analysis. * **Schema enforcement:** Lakehouses enforce schemas to ensure spatial data consistency by guaranteeing correct -geometry types and attribute formats, which improve data quality and query reliability. +geometry types and attribute formats, improving data quality and query reliability. * **Database optimizations:** Techniques like geographic partitioning, data skipping using bounding boxes, and columnar storage can accelerate spatial queries and improve storage efficiency within Lakehouses. @@ -54,7 +54,8 @@ bounding boxes, and columnar storage can accelerate spatial queries and improve Lakehouse architectures use open table formats like Apache Iceberg, Delta Lake, or Hudi to manage data stored on underlying platforms like cloud object storage (for example Amazon S3 or Google Cloud Storage). -Lakehouse Tables are governed by a catalog. These catalogs don't store files since files are kept in cloud object storage. +Tables in a lakehouse are governed by a catalog. The catalog doesn't store the data files +themselves; those remain in cloud object storage. Rather, catalogs maintain records of related metadata information, like names of available databases/tables, table schemas, versioning information needed for features like time travel. @@ -73,24 +74,23 @@ data science, machine learning, and other complex analyses. The Lakehouse Architecture offers several advantages: -* Data is stored in open formats, letting any engine can query it, avoiding vendor lock-in. +* Data is stored in open formats, letting any compatible engine query it, avoiding vendor lock-in. * Lakehouses support all the features familiar to data warehouses, like reliable transactions, Data Manipulation Language (DML) operations, and RBAC. * Lakehouses are performant enough for low-latency applications like BI dashboards. * Lakehouses are interoperable with proprietary tools like BigQuery, Redshift, or Esri. * Lakehouses leverage the cost-efficiency of cloud object storage (similar to data lakes) for data storage. -* Lakehouses are highly compatible with existing compute engines. - * You can use one engine for ingestion, another for ETL, and a third for Machine Learning. +* Lakehouses' compatibility with multiple compute engines allows you to use one engine for ingestion, another for ETL, and a third for Machine Learning. -## Lakehouses & spatial data +## Lakehouses and spatial data -Earlier, we mentioned 2 important features of Lakehouses: Single-table transactions and RBAC. -Let's discuss how these 2 features can be beneficial for working with spatial data. +Earlier, we mentioned two important features of Lakehouses: Single-table transactions and RBAC. +Let's discuss how these two features can be beneficial for working with spatial data. ### An example of single-table transactions Imagine a retail company is closing a store in New Jersey. -Let's assume that this company maintains 3 different tables: +Let's assume that this company maintains three different tables: 1. `stores`(containing point geometry) 1. `sales_territories` (containing polygon geometry) @@ -163,7 +163,7 @@ For example, the `stores` table won't be left in a partially updated state. To ensure data consistency across all three tables (`stores`, `sales_territories`, and `sales_performance`) for the entire business operation of closing a store, these individual atomic operations on each table would typically be executed in sequence. -Single-table transactions don't automatically "package" these three distinct table updates into a single overarching transaction, +Single-table transactions don't automatically "package" these three distinct table updates into a single overarching transaction, as that would require multi-table transaction capabilities. However, by ensuring that each step is completed successfully and atomically, the overall process is far more reliable. @@ -173,7 +173,7 @@ remain consistent, and the `sales_territories` table would roll back its own fai Now, let's see how Lakehouses differ from Data Lakes. -## Lakehouses vs. Data Lakes +## Lakehouses versus data lakes In contrast to Data Lakehouses, Data Lakes store data in files without a metadata layer, so they don't guarantee reliable transactions. @@ -196,7 +196,7 @@ The Lakehouse metadata layer is relatively small, effectively making storage cos of a Data Lake. However, because Lakehouses allow for better query performance, you can generally expect lower compute costs compared to Data Lakes. -## Lakehouses vs. Data Warehouses +## Lakehouses versus data warehouses A Data Warehouse is an analytics system typically powered by a proprietary engine with similarly proprietary file formats. @@ -208,11 +208,11 @@ Still, data warehouses generally exhibit the following limitations: * Pricing models frequently package storage and compute, which could require users to pay for more compute even if they only need more storage. * Storing data in proprietary file formats limits compatibility with other engines. -* Querying data stored in open file formats _can_ result in slower performance compared - to proprietary formats due to being built specifically for a specific compute engine. +* Querying data stored in open file formats _can_ result in slower performance compared to querying its + native proprietary format, which is optimized for its specific compute engine. * Performance can suffer in shared compute environments when resource-intensive queries from one user impact others. -Many modern enterprises prefer the Lakehouse architecture because Data Lakehouses +Many modern enterprises prefer the Lakehouse architecture because lakehouses are vendor-neutral, low-cost, and compatible with different compute engines. In the next section, we'll discuss how to create an Apache Iceberg table, an @@ -257,7 +257,7 @@ sedona.table("local.db.customers").show() ``` As you can see, creating a table with tabular data is straightforward. -Now, let's see how to make a table with spatial data in Apache Iceberg. +Now, let's see how to create a table with spatial data in Apache Iceberg. ## Creating spatial columns in Apache Iceberg v3 @@ -319,7 +319,7 @@ joined.show() Now, we can see the customer information and the location of their purchases all in one table. -You can join tables with Sedona, regardless of that table's underlying file +You can join tables with Sedona, regardless of the tables' underlying file format, because Sedona has so many built-in file readers. For example, with Apache Sedona, you can join one table loaded from Shapefiles @@ -327,7 +327,7 @@ with another table loaded from GeoParquet, an extension of the open source Apach !!!tip "Best Practice: Co-location can optimize spatial tables in Lakehouses" Consider the following co-location tips to optimize your spatial data queries: - *To speed up your Lakehouse queries, you can co-locate similar data in the same files and eliminate excessively small files. + * To speed up your Lakehouse queries, store similar data in the same files and eliminate excessively small files. * Use Apache Iceberg to store the tabular and spatial tables in the same catalog. Let's look at the following GeoParquet table. This table is the Overture Maps Foundation buildings dataset. @@ -437,11 +437,11 @@ print(f"Query on Iceberg table returned {iceberg_count} results.") print(f"(Count comparison: Source View={source_count}, Iceberg Table={iceberg_count})") ``` -## Spatial tables in Data lakes +## Spatial tables in data lakes Let's compare these Iceberg Lakehouse tables with spatial tables built with data lakes. -### Lack of Atomicity +### Lack of atomicity ```py import os @@ -587,7 +587,7 @@ print(f"Query on final data lake view returned {final_dl_count} results (took {e ## Conclusion Lakehouse architecture offers many advantages for the data community, and -with the native support native for `geometry` and `geography` (GEO) data types +with native support for `geometry` and `geography` (GEO) data types in Iceberg, the spatial community can now take advantage of these benefits. This marks a fundamental shift from simply storing spatial data _within_ generic types (like binary WKB or string WKT). From 550fafaf4ad31896e44ca39e7a6e463d6361ce11 Mon Sep 17 00:00:00 2001 From: Kelly-Ann Dolor Date: Wed, 30 Jul 2025 13:58:21 -0700 Subject: [PATCH 30/31] fixing typos etc for review --- docs/blog/posts/spatial-tables-data-lakehouses.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/blog/posts/spatial-tables-data-lakehouses.md b/docs/blog/posts/spatial-tables-data-lakehouses.md index c4e0a53e85b..06fd46a0f00 100644 --- a/docs/blog/posts/spatial-tables-data-lakehouses.md +++ b/docs/blog/posts/spatial-tables-data-lakehouses.md @@ -327,8 +327,10 @@ with another table loaded from GeoParquet, an extension of the open source Apach !!!tip "Best Practice: Co-location can optimize spatial tables in Lakehouses" Consider the following co-location tips to optimize your spatial data queries: - * To speed up your Lakehouse queries, store similar data in the same files and eliminate excessively small files. - * Use Apache Iceberg to store the tabular and spatial tables in the same catalog. +
    +
  • To speed up your Lakehouse queries, store similar data in the same files and eliminate excessively small files.
  • +
  • Use Apache Iceberg to store the tabular and spatial tables in the same catalog.
  • +
Let's look at the following GeoParquet table. This table is the Overture Maps Foundation buildings dataset. From 53977d679a3fa6dbc35b8688d01ecb1033f65b13 Mon Sep 17 00:00:00 2001 From: Kelly-Ann Dolor Date: Wed, 30 Jul 2025 16:52:50 -0700 Subject: [PATCH 31/31] wrapping code samples --- .../posts/spatial-tables-data-lakehouses.md | 196 +++++++++++++----- 1 file changed, 141 insertions(+), 55 deletions(-) diff --git a/docs/blog/posts/spatial-tables-data-lakehouses.md b/docs/blog/posts/spatial-tables-data-lakehouses.md index 06fd46a0f00..b150aa3b699 100644 --- a/docs/blog/posts/spatial-tables-data-lakehouses.md +++ b/docs/blog/posts/spatial-tables-data-lakehouses.md @@ -135,7 +135,7 @@ erDiagram SALES_TERRITORIES { int territory_id PK "Primary Key" varchar territory_name "Name of the sales territory" - polygon geometry "Polygon geometry defining the territory boundary" + polygon geometry "Polygon geometry defining
the territory boundary" } STORES { @@ -143,17 +143,17 @@ erDiagram varchar store_name "Name of the store" varchar address "Store's address" varchar city "City" - varchar state "State (e.g., NJ for New Jersey)" - point geometry "Point geometry for store location" - int territory_id FK "Foreign Key referencing sales_territories.territory_id" + varchar state "State
(e.g., NJ for New Jersey)" + point geometry "Point geometry
for store location" + int territory_id FK "Foreign Key referencing
sales_territories.territory_id" } SALES_PERFORMANCE { int performance_id PK "Primary Key" - int store_id FK "Foreign Key referencing stores.store_id" + int store_id FK "Foreign Key referencing
stores.store_id" date sale_date "Date of sales data" decimal sales_amount "Sales figures" - varchar other_details "Other non-geometric performance data" + varchar other_details "Other non-geometric
performance data" } ``` @@ -267,8 +267,12 @@ in tables without any special data accommodations. Let's create a `customer_purchases` table with a `purchase_location` column that contains the `Point` Geometry of the different store locations: -```py -CREATE TABLE local.db.customer_purchases (id string, price double, geometry geometry) +```sql +CREATE TABLE local.db.customer_purchases ( + id string, + price double, + geometry geometry +) USING iceberg TBLPROPERTIES('format-version'='3'); ``` @@ -276,6 +280,7 @@ TBLPROPERTIES('format-version'='3'); Now, let's append the location data to the table: ```py +# A list of coordinates defining a polygon coords = [ (-88.110352, 24.006326), (-77.080078, 24.006326), @@ -283,13 +288,22 @@ coords = [ (-88.110352, 31.503629), (-88.110352, 24.006326) ] -df = sedona.createDataFrame([ - ("a", 10.99, Polygon(coords)), - ("b", 3.5, Point(1, 2)), - ("c", 1.95, Point(3, 4)), -], ["id", "price", "geometry"]) -df.write.format("iceberg").mode("append").saveAsTable("local.db.customer_purchases") +# Create a SedonaDataFrame with sample data +df = sedona.createDataFrame( + [ + ("a", 10.99, Polygon(coords)), + ("b", 3.5, Point(1, 2)), + ("c", 1.95, Point(3, 4)), + ], + ["id", "price", "geometry"] +) + +# Write the DataFrame to an Iceberg table +df.write \ + .format("iceberg") \ + .mode("append") \ + .saveAsTable("local.db.customer_purchases") ``` The spatial table uses `Point` geometries for exact purchase locations @@ -341,7 +355,10 @@ from pyspark.sql.functions import col, expr # Define the Overture Maps source path overture_release = "2025-03-19.0" -overture_s3_path = f"s3://overturemaps-us-west-2/release/{overture_release}/theme=buildings/type=building" +overture_s3_path = ( + f"s3://overturemaps-us-west-2/release/" + f"{overture_release}/theme=buildings/type=building" +) print(f"Reading Overture buildings data from: {overture_s3_path}") @@ -368,50 +385,73 @@ Let's run a filtering query on this GeoParquet dataset: ```py # Define the area of interest polygon (WKT format) -spot = "POLYGON((-82.258759 29.129371, -82.180481 29.136569, -82.202454 29.173747, -82.258759 29.129371))" +spot = ( + "POLYGON((-82.258759 29.129371, -82.180481 29.136569, " + "-82.202454 29.173747, -82.258759 29.129371))" +) # Construct the SQL query using the temporary view name sql_query_source = f""" SELECT id, height FROM {source_view_name} -WHERE ST_Contains(ST_GeomFromWKT('{spot}'), geometry) +WHERE ST_Contains( + ST_GeomFromWKT('{spot}'), + geometry +) """ -print(f"Running spatial query on source data view '{source_view_name}'...") +print( + f"Running spatial query on source data view '{source_view_name}'..." +) # Execute the query and get the count source_results_df = sedona.sql(sql_query_source) source_count = source_results_df.count() -print(f"Query on source GeoParquet data returned {source_count} results.") + +print( + f"Query on source GeoParquet data returned {source_count} results." +) ``` Let's convert this dataset to Iceberg: ```py # Assume 'sedona', 'buildings_df', 'overture_release', -# and 'iceberg_db_name' (e.g., 'blog_db') exist from previous sections +# and 'iceberg_db_name' exist from previous sections. -# Define the full Iceberg table name -iceberg_table_full_name = f"{iceberg_db_name}.overture_buildings_{overture_release.replace('-', '_').replace('.', '_')}" +# Format the release version string to be used in the table name +release_suffix = overture_release.replace('-', '_').replace('.', '_') +iceberg_table_full_name = ( + f"{iceberg_db_name}.overture_buildings_{release_suffix}" +) print(f"Preparing Iceberg conversion for: {iceberg_table_full_name}") -# Define minimal Iceberg table DDL string +# Define Iceberg table DDL, breaking the long line for readability sql_create_iceberg = f""" -CREATE TABLE IF NOT EXISTS {iceberg_table_full_name} (id STRING, geometry GEOMETRY, height DOUBLE) -USING iceberg PARTITIONED BY (bucket(16, id)) TBLPROPERTIES('format-version'='2'); +CREATE TABLE IF NOT EXISTS {iceberg_table_full_name} ( + id STRING, + geometry GEOMETRY, + height DOUBLE +) +USING iceberg +PARTITIONED BY (bucket(16, id)) +TBLPROPERTIES('format-version'='2'); """ -# Create the Iceberg table structure + +# Create the empty Iceberg table structure sedona.sql(sql_create_iceberg) -# Write the DataFrame to the Iceberg table, overwriting if it exists -(buildings_df +# Write the DataFrame to the Iceberg table +( + buildings_df .select("id", "geometry", "height") .write .format("iceberg") .mode("overwrite") .saveAsTable(iceberg_table_full_name) ) + print(f"Successfully wrote data to Iceberg table: {iceberg_table_full_name}") ``` @@ -425,18 +465,27 @@ Now, let's rerun the same query on the Iceberg table: sql_query_iceberg = f""" SELECT id, height FROM {iceberg_table_full_name} -WHERE ST_Contains(ST_GeomFromWKT('{spot}'), geometry) +WHERE ST_Contains( + ST_GeomFromWKT('{spot}'), + geometry +) """ -print(f"Running spatial query on Iceberg table '{iceberg_table_full_name}'...") +print( + f"Running spatial query on Iceberg table '{iceberg_table_full_name}'..." +) # Execute the query and get the count iceberg_results_df = sedona.sql(sql_query_iceberg) iceberg_count = iceberg_results_df.count() + print(f"Query on Iceberg table returned {iceberg_count} results.") # Compare counts if desired -print(f"(Count comparison: Source View={source_count}, Iceberg Table={iceberg_count})") +print( + f"(Count comparison: Source View={source_count}, " + f"Iceberg Table={iceberg_count})" +) ``` ## Spatial tables in data lakes @@ -476,34 +525,51 @@ import time # Write the initial data to the data lake path to simulate its existence print("Simulating initial data presence in data lake path...") -(buildings_df +( + buildings_df # Convert geometry to WKB for standard Parquet storage .withColumn("geometry_wkb", expr("ST_AsWKB(geometry)")) .select("id", "height", "geometry_wkb") - .write.format("parquet").save(data_lake_path) + .write + .format("parquet") + .save(data_lake_path) ) print(f"Initial data written to {data_lake_path}") # Create a temporary view on this data lake path source_dl_view_name = "buildings_datalake_source_view" -(sedona.read.parquet(data_lake_path) +( + sedona.read + .parquet(data_lake_path) .withColumn("geometry", expr("ST_GeomFromWKB(geometry_wkb)")) .createOrReplaceTempView(source_dl_view_name) ) print(f"Created view '{source_dl_view_name}' on data lake path.") # Run the same spatial filter query -spot_wkt = "POLYGON((-82.258759 29.129371, -82.180481 29.136569, -82.202454 29.173747, -82.258759 29.129371))" +spot_wkt = ( + "POLYGON((-82.258759 29.129371, -82.180481 29.136569, " + "-82.202454 29.173747, -82.258759 29.129371))" +) + sql_query_source = f""" SELECT id, height FROM {source_dl_view_name} -WHERE ST_Contains(ST_GeomFromWKT('{spot_wkt}'), geometry) +WHERE ST_Contains( + ST_GeomFromWKT('{spot_wkt}'), + geometry +) """ + print(f"Running spatial query on '{source_dl_view_name}'...") start_time = time.time() source_dl_count = sedona.sql(sql_query_source).count() end_time = time.time() -print(f"Query on data lake view returned {source_dl_count} results (took {end_time - start_time:.2f}s).") + +print( + f"Query on data lake view returned {source_dl_count} results " + f"(took {end_time - start_time:.2f}s)." +) ``` ### Updating the dataset @@ -522,7 +588,10 @@ or loss if the write operation fails partway through. from pyspark.sql.functions import expr import time -print(f"Simulating an UPDATE on the data lake table (via Read-Modify-Rewrite)...") +print( + "Simulating an UPDATE on the data lake table " + "(via Read-Modify-Rewrite)..." +) print(f"Goal: Add 'is_in_spot' column to data in {data_lake_path}") # Read the *entire* dataset again @@ -531,7 +600,9 @@ current_data_df = ( sedona.read.parquet(data_lake_path) .withColumn("geometry", expr("ST_GeomFromWKB(geometry_wkb)")) ) -read_count = current_data_df.cache().count() # Cache for potential reuse & count + +# Cache for potential reuse & count +read_count = current_data_df.cache().count() print(f"Read {read_count} records.") # Add the new column @@ -540,18 +611,18 @@ modified_data_df = current_data_df.withColumn( "is_in_spot", expr(f"ST_Contains(ST_GeomFromWKT('{spot_wkt}'), geometry)") ) -print(f"Rewriting ENTIRE dataset ({read_count} records) with new column back to {data_lake_path}...") -start_time_write = time.time() -(modified_data_df - .withColumn("geometry_wkb", expr("ST_AsWKB(geometry)")) # Convert back for storage - .select("id", "height", "geometry_wkb", "is_in_spot") - .write.format("parquet").mode("overwrite").save(data_lake_path) + +print( + f"Rewriting ENTIRE dataset ({read_count} records) " + f"with new column back to {data_lake_path}..." ) -end_time_write = time.time() -print(f"Data lake overwrite complete (took {end_time_write - start_time_write:.2f}s).") -current_data_df.unpersist() -print("DISADVANTAGE 1 (Inefficiency): Update required full read & full rewrite.") -print("DISADVANTAGE 2 (No Atomicity): If the overwrite failed, data is lost/corrupted.") +start_time_write = time.time() + +( + modified_data_df + # Convert back to WKB for standard Parquet storage + .withColumn("geometry_wkb", expr("ST_AsWKB(geometry)")) + .select("id", "height", "geometry_wkb", "is_in ``` ### Querying "updated" table @@ -567,23 +638,38 @@ nature of the raw files, contrasts with the simple, direct query against an alre from pyspark.sql.functions import expr import time -print(f"Running spatial query on the overwritten data lake path '{data_lake_path}'...") +print( + "Running spatial query on the overwritten data lake path " + f"'{data_lake_path}'..." +) final_dl_view_name = "buildings_datalake_final_view" -(sedona.read.parquet(data_lake_path) +( + sedona.read.parquet(data_lake_path) .withColumn("geometry", expr("ST_GeomFromWKB(geometry_wkb)")) .createOrReplaceTempView(final_dl_view_name) ) sql_query_final = f""" -SELECT id, height, is_in_spot -- Can now select the new column +SELECT + id, + height, + is_in_spot -- Can now select the new column FROM {final_dl_view_name} -WHERE ST_Contains(ST_GeomFromWKT('{spot_wkt}'), geometry) +WHERE ST_Contains( + ST_GeomFromWKT('{spot_wkt}'), + geometry +) """ + start_time_final = time.time() final_dl_count = sedona.sql(sql_query_final).count() end_time_final = time.time() -print(f"Query on final data lake view returned {final_dl_count} results (took {end_time_final - start_time_final:.2f}s).") + +print( + f"Query on final data lake view returned {final_dl_count} results " + f"(took {end_time_final - start_time_final:.2f}s)." +) ``` ## Conclusion