diff --git a/felt_python/__init__.py b/felt_python/__init__.py index 916e9a4..a76dd91 100644 --- a/felt_python/__init__.py +++ b/felt_python/__init__.py @@ -7,6 +7,7 @@ move_map, create_embed_token, add_source_layer, + duplicate_map, # Deprecated get_map_details, ) @@ -47,6 +48,7 @@ from .layer_groups import ( list_layer_groups, get_layer_group, + update_layer_group, update_layer_groups, delete_layer_group, publish_layer_group, @@ -89,6 +91,7 @@ "move_map", "create_embed_token", "add_source_layer", + "duplicate_map", # Layers "list_layers", "upload_file", @@ -110,6 +113,7 @@ # Layer groups "list_layer_groups", "get_layer_group", + "update_layer_group", "update_layer_groups", "delete_layer_group", "publish_layer_group", diff --git a/felt_python/layer_groups.py b/felt_python/layer_groups.py index e96df51..483a330 100644 --- a/felt_python/layer_groups.py +++ b/felt_python/layer_groups.py @@ -100,6 +100,50 @@ def delete_layer_group( ) +def update_layer_group( + map_id: str, + layer_group_id: str, + name: str = None, + caption: str = None, + ordering_key: int = None, + visibility_interaction: str = None, + api_token: str | None = None, +): + """Update a single layer group + + Args: + map_id: The ID of the map containing the layer group + layer_group_id: The ID of the layer group to update + name: Optional new name for the layer group + caption: Optional new caption for the layer group + ordering_key: Optional new ordering key for positioning + visibility_interaction: Optional visibility interaction setting + ("default", "slider") + api_token: Optional API token + + Returns: + The updated layer group + """ + json_payload = {} + + if name is not None: + json_payload["name"] = name + if caption is not None: + json_payload["caption"] = caption + if ordering_key is not None: + json_payload["ordering_key"] = ordering_key + if visibility_interaction is not None: + json_payload["visibility_interaction"] = visibility_interaction + + response = make_request( + url=GROUP.format(map_id=map_id, layer_group_id=layer_group_id), + method="POST", + json=json_payload, + api_token=api_token, + ) + return json.load(response) + + def publish_layer_group( map_id: str, layer_group_id: str, diff --git a/felt_python/maps.py b/felt_python/maps.py index 11b41ac..3827e92 100644 --- a/felt_python/maps.py +++ b/felt_python/maps.py @@ -14,6 +14,7 @@ MAP_MOVE = urljoin(BASE_URL, "maps/{map_id}/move") MAP_EMBED_TOKEN = urljoin(BASE_URL, "maps/{map_id}/embed_token") MAP_ADD_SOURCE_LAYER = urljoin(BASE_URL, "maps/{map_id}/add_source_layer") +MAP_DUPLICATE = urljoin(BASE_URL, "maps/{map_id}/duplicate") def create_map( @@ -230,3 +231,45 @@ def add_source_layer( api_token=api_token, ) return json.load(response) + + +def duplicate_map( + map_id: str, + title: str = None, + project_id: str = None, + folder_id: str = None, + api_token: str = None, +): + """Duplicate a map + + Args: + map_id: The ID of the map to duplicate + title: Optional title for the duplicated map + project_id: The ID of the project to place the duplicated map in + (mutually exclusive with folder_id) + folder_id: The ID of the folder to place the duplicated map in + (mutually exclusive with project_id) + api_token: Optional API token + + Returns: + The duplicated map + """ + if project_id is not None and folder_id is not None: + raise ValueError("Cannot specify both project_id and folder_id") + + json_args = {} + if title is not None: + json_args["title"] = title + + if project_id is not None: + json_args["destination"] = {"project_id": project_id} + elif folder_id is not None: + json_args["destination"] = {"folder_id": folder_id} + + response = make_request( + url=MAP_DUPLICATE.format(map_id=map_id), + method="POST", + json=json_args, + api_token=api_token, + ) + return json.load(response) diff --git a/notebooks/layer_groups.ipynb b/notebooks/layer_groups.ipynb index dead264..44f40b1 100644 --- a/notebooks/layer_groups.ipynb +++ b/notebooks/layer_groups.ipynb @@ -21,6 +21,7 @@ " delete_map,\n", " list_layer_groups,\n", " get_layer_group,\n", + " update_layer_group,\n", " update_layer_groups,\n", " delete_layer_group,\n", " publish_layer_group,\n", @@ -204,14 +205,41 @@ "\n", "result = update_layer_groups(map_id, updated_groups)\n", "result" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Update layers to assign them to groups\n" - ] + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Update a single layer group\n", + "\n", + "You can also update individual layer groups instead of doing bulk updates" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Update just the first group individually\n", + "individual_update_result = update_layer_group(\n", + " map_id=map_id,\n", + " layer_group_id=group1_id,\n", + " name=\"Vector Data (Individual Update)\",\n", + " caption=\"Updated via individual update function\",\n", + " ordering_key=10,\n", + " visibility_interaction=\"slider\"\n", + ")\n", + "individual_update_result" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Update layers to assign them to groups\n" + ] }, { "cell_type": "code", @@ -222,11 +250,11 @@ "# Prepare updates for both layers\n", "layer_updates = [\n", " {\n", - " \"id\": layer1_id\n", + " \"id\": layer1_id,\n", " \"layer_group_id\": group1_id,\n", " },\n", " {\n", - " \"id\": layer2_id\n", + " \"id\": layer2_id,\n", " \"layer_group_id\": group2_id,\n", " }\n", "]\n", @@ -316,4 +344,4 @@ }, "nbformat": 4, "nbformat_minor": 4 -} \ No newline at end of file +} diff --git a/notebooks/maps.ipynb b/notebooks/maps.ipynb index 457cb93..1f6eb38 100644 --- a/notebooks/maps.ipynb +++ b/notebooks/maps.ipynb @@ -25,7 +25,8 @@ " resolve_comment,\n", " delete_comment,\n", " create_embed_token,\n", - " add_source_layer\n", + " add_source_layer,\n", + " duplicate_map\n", ")\n", "\n", "os.environ[\"FELT_API_TOKEN\"] = \"\"" @@ -183,6 +184,33 @@ "print(f\"Expires at: {embed_token['expires_at']}\")" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Duplicating a map\n", + "\n", + "You can duplicate an existing map to create a copy with optional title and destination." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "duplicated_map = duplicate_map(\n", + " map_id=map_id,\n", + " title=\"Duplicated felt-python map\"\n", + " # project_id=\"project_id_here\" # Optional: specify destination project\n", + " # folder_id=\"folder_id_here\" # Optional: specify destination folder\n", + ")\n", + "\n", + "duplicated_map_id = duplicated_map[\"id\"]\n", + "print(f\"Duplicated map created with ID: {duplicated_map_id}\")\n", + "print(f\"Duplicated map URL: {duplicated_map['url']}\")" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -196,7 +224,10 @@ "metadata": {}, "outputs": [], "source": [ - "delete_map(map_id)" + "delete_map(map_id)\n", + "\n", + "# Also delete the duplicated map\n", + "delete_map(duplicated_map_id)" ] } ], diff --git a/tests/layer_groups_test.py b/tests/layer_groups_test.py index a85e50e..1ca83e2 100644 --- a/tests/layer_groups_test.py +++ b/tests/layer_groups_test.py @@ -15,6 +15,7 @@ create_map, list_layer_groups, get_layer_group, + update_layer_group, update_layer_groups, publish_layer_group, update_layers, @@ -191,7 +192,27 @@ def test_layer_groups_workflow(self): print("Layer groups updated successfully") - # Step 7: Update layers to assign them to groups + # Step 7: Test individual layer group update + print("Testing individual layer group update...") + + individual_update_result = update_layer_group( + map_id=map_id, + layer_group_id=group1_id, + name="Vector Data (Individual Update)", + caption="Updated via individual update function", + ordering_key=10, + visibility_interaction="slider" + ) + + self.assertIsNotNone(individual_update_result) + self.assertEqual(individual_update_result["name"], "Vector Data (Individual Update)") + self.assertEqual(individual_update_result["caption"], "Updated via individual update function") + self.assertEqual(individual_update_result["ordering_key"], 10) + self.assertEqual(individual_update_result["visibility_interaction"], "slider") + + print("Individual layer group update completed successfully") + + # Step 8: Update layers to assign them to groups print("Updating layers to assign them to groups...") layer_updates = [ { @@ -221,7 +242,7 @@ def test_layer_groups_workflow(self): any(layer["id"] == layer2_id for layer in group2_details["layers"]) ) - # Step 8: Publish a layer group to the library + # Step 9: Publish a layer group to the library print(f"Publishing layer group: {group1_id} to the library...") try: published_group = publish_layer_group( diff --git a/tests/maps_test.py b/tests/maps_test.py index 8db6092..bf90831 100644 --- a/tests/maps_test.py +++ b/tests/maps_test.py @@ -20,6 +20,7 @@ resolve_comment, delete_comment, create_embed_token, + duplicate_map, ) @@ -134,6 +135,28 @@ def test_map_workflow(self): self.assertIn("token", token_data) self.assertIn("expires_at", token_data) print(f"Created embed token that expires at {token_data['expires_at']}") + + # Step 8: Duplicate the map + print("Duplicating map...") + + duplicated_map_name = f"Duplicated Map ({self.timestamp})" + duplicated_map = duplicate_map( + map_id=map_id, + title=duplicated_map_name + ) + + self.assertIsNotNone(duplicated_map) + self.assertIn("id", duplicated_map) + self.assertEqual(duplicated_map["title"], duplicated_map_name) + self.assertNotEqual(duplicated_map["id"], map_id) # Should be different ID + + duplicated_map_id = duplicated_map["id"] + print(f"Duplicated map created with ID: {duplicated_map_id}") + + # Clean up duplicated map + print("Cleaning up duplicated map...") + delete_map(duplicated_map_id) + print("\nTest completed successfully!")