AnyMap Multi-Cell Rendering Test¶
This notebook thoroughly tests the anymap package's ability to render interactive maps across multiple cells without issues. It also tests layer persistence, bidirectional communication, and various MapLibre features.
# Import required modules
from anymap import MapLibreMap
import json
import time
print("AnyMap version loaded successfully!")
AnyMap version loaded successfully!
Test 1: Basic Map Creation and Multi-Cell Rendering¶
Let's create a map and test that it renders correctly across multiple cells.
# Create the main test map
m = MapLibreMap(
center=[37.7749, -122.4194], zoom=12, height="500px", width="100%" # San Francisco
)
print("Map created. Displaying first time...")
m
Map created. Displaying first time...
# Display the same map instance again - should render without issues
print("Displaying the SAME map instance in a second cell...")
m
Displaying the SAME map instance in a second cell...
Test 2: Layer Persistence¶
Now let's add some layers and verify they persist when the map is rendered in multiple cells.
# Add a GeoJSON layer with points of interest in San Francisco
sf_poi = {
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"geometry": {"type": "Point", "coordinates": [-122.4194, 37.7749]},
"properties": {"name": "San Francisco City Hall", "type": "government"},
},
{
"type": "Feature",
"geometry": {"type": "Point", "coordinates": [-122.4183, 37.7793]},
"properties": {"name": "Golden Gate Park", "type": "park"},
},
{
"type": "Feature",
"geometry": {"type": "Point", "coordinates": [-122.3959, 37.7937]},
"properties": {"name": "Fisherman's Wharf", "type": "attraction"},
},
],
}
# Add the layer
m.add_geojson_layer(
layer_id="sf_poi",
geojson_data=sf_poi,
layer_type="circle",
paint={
"circle-radius": 8,
"circle-color": "#ff6b6b",
"circle-stroke-width": 2,
"circle-stroke-color": "#ffffff",
},
)
# Add markers
m.add_marker(37.7749, -122.4194, popup="<b>City Hall</b><br>Government building")
m.add_marker(37.7793, -122.4183, popup="<b>Golden Gate Park</b><br>Beautiful park")
print("Added GeoJSON layer and markers. Current layers:", list(m.get_layers().keys()))
Government building") m.add_marker(37.7793, -122.4183, popup="Golden Gate Park
Beautiful park") print("Added GeoJSON layer and markers. Current layers:", list(m.get_layers().keys()))
Added GeoJSON layer and markers. Current layers: ['sf_poi']
# Render the map again - layers should persist
print("Rendering map with layers in a new cell...")
print("Layers should be visible:", list(m.get_layers().keys()))
print("Sources should include:", list(m.get_sources().keys()))
m
Rendering map with layers in a new cell... Layers should be visible: ['sf_poi'] Sources should include: ['sf_poi_source']
m
Test 3: Dynamic Map Modifications¶
Test that modifications to the map work correctly across cells.
# Modify map properties
m.fly_to(37.8044, -122.2712, zoom=15) # Fly to Berkeley
m.set_bearing(45) # Rotate the map
m.set_pitch(45) # Tilt the map
print("Modified map view - flying to Berkeley with rotation and tilt")
Modified map view - flying to Berkeley with rotation and tilt
# Add more layers to test different layer types
berkeley_area = {
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"geometry": {
"type": "Polygon",
"coordinates": [
[
[-122.2850, 37.8000],
[-122.2700, 37.8000],
[-122.2700, 37.8100],
[-122.2850, 37.8100],
[-122.2850, 37.8000],
]
],
},
"properties": {"name": "Berkeley Area"},
}
],
}
# Add polygon layer
m.add_geojson_layer(
layer_id="berkeley_area",
geojson_data=berkeley_area,
layer_type="fill",
paint={"fill-color": "#4a90e2", "fill-opacity": 0.3},
)
print("Added Berkeley area polygon layer")
Added Berkeley area polygon layer
m
m
# Display map again with all modifications
print("Displaying map with all modifications and layers:")
print("Current layers:", list(m.get_layers().keys()))
m
Displaying map with all modifications and layers: Current layers: ['sf_poi', 'berkeley_area']
Test 4: Multiple Independent Map Instances¶
Test creating multiple independent map instances that work independently.
# Create a second independent map
m2 = MapLibreMap(center=[40.7128, -74.0060], zoom=12, height="600px") # New York City
# Add different data to the second map
nyc_poi = {
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"geometry": {"type": "Point", "coordinates": [-74.0060, 40.7128]},
"properties": {"name": "Times Square", "type": "attraction"},
},
{
"type": "Feature",
"geometry": {"type": "Point", "coordinates": [-73.9857, 40.7484]},
"properties": {"name": "Central Park", "type": "park"},
},
],
}
m2.add_geojson_layer(
layer_id="nyc_poi",
geojson_data=nyc_poi,
layer_type="circle",
paint={
"circle-radius": 10,
"circle-color": "#50C878",
"circle-stroke-width": 2,
"circle-stroke-color": "#ffffff",
},
)
print("Created second map (NYC) with different layers")
print("Map 1 layers:", list(m.get_layers().keys()))
print("Map 2 layers:", list(m2.get_layers().keys()))
m2
Created second map (NYC) with different layers Map 1 layers: ['sf_poi', 'berkeley_area'] Map 2 layers: ['nyc_poi']
# Display both maps side by side to verify independence
print("Original map (San Francisco/Berkeley area):")
print("Layers:", list(m.get_layers().keys()))
m
Original map (San Francisco/Berkeley area): Layers: ['sf_poi', 'berkeley_area']
# Second map again
print("Second map (New York City):")
print("Layers:", list(m2.get_layers().keys()))
m2
Second map (New York City): Layers: ['nyc_poi']
Test 5: Event Handling and Bidirectional Communication¶
Test the event handling system and bidirectional communication.
# Set up event handlers
click_events = []
move_events = []
def on_map_click(event):
lat, lng = event["lngLat"]
click_events.append(f"Clicked at: {lat:.4f}, {lng:.4f}")
print(f"Map clicked at: {lat:.4f}, {lng:.4f}")
def on_map_move(event):
center = event.get("center", [0, 0])
zoom = event.get("zoom", 0)
move_events.append(f"Moved to: {center[0]:.4f}, {center[1]:.4f} at zoom {zoom:.2f}")
print(f"Map moved to: {center[0]:.4f}, {center[1]:.4f} at zoom {zoom:.2f}")
# Register event handlers
m.on_map_event("click", on_map_click)
m.on_map_event("moveend", on_map_move)
print("Event handlers registered. Click and move the map to test!")
print("Events will be captured and displayed here.")
Event handlers registered. Click and move the map to test! Events will be captured and displayed here.
# Display the map with event handlers
print("Interactive map with event handlers - try clicking and panning!")
m
Interactive map with event handlers - try clicking and panning!
# Check collected events
print("Recent click events:")
for event in click_events[-5:]: # Show last 5
print(f" {event}")
print("\nRecent move events:")
for event in move_events[-5:]: # Show last 5
print(f" {event}")
print(f"\nTotal click events: {len(click_events)}")
print(f"Total move events: {len(move_events)}")
Recent click events: Recent move events: Total click events: 0 Total move events: 0
Test 6: Layer Management Methods¶
Test the enhanced layer management functionality.
# Test layer management methods
print("Current layers before cleanup:", list(m.get_layers().keys()))
print("Current sources before cleanup:", list(m.get_sources().keys()))
# Create a new map for testing layer management
m3 = MapLibreMap(center=[51.5074, -0.1278], zoom=12, height="600px") # London
# Add multiple test layers
test_data = {
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"geometry": {"type": "Point", "coordinates": [-0.1278, 51.5074]},
"properties": {"name": "Big Ben"},
}
],
}
m3.add_geojson_layer("test1", test_data, "circle", {"circle-color": "red"})
m3.add_geojson_layer("test2", test_data, "circle", {"circle-color": "blue"})
m3.add_geojson_layer("test3", test_data, "circle", {"circle-color": "green"})
print("\nLayers after adding test layers:", list(m3.get_layers().keys()))
m3
Current layers before cleanup: ['sf_poi', 'berkeley_area'] Current sources before cleanup: ['sf_poi_source', 'berkeley_area_source'] Layers after adding test layers: ['test1', 'test2', 'test3']
# Test clearing layers
print("Before clearing - Layers:", list(m3.get_layers().keys()))
m3.clear_layers()
print("After clearing layers - Layers:", list(m3.get_layers().keys()))
print("Sources still exist:", list(m3.get_sources().keys()))
# Display map after clearing layers
m3
Before clearing - Layers: ['test1', 'test2', 'test3'] After clearing layers - Layers: [] Sources still exist: ['test1_source', 'test2_source', 'test3_source']
Test 7: Final Multi-Cell Persistence Test¶
Let's do a final comprehensive test to ensure everything works across cells.
# Final test - render all three maps in sequence
print("=== FINAL MULTI-CELL TEST ===")
print("\n1. Original map (San Francisco area):")
print(" Layers:", list(m.get_layers().keys()))
print(" Center:", m.center, "Zoom:", m.zoom)
m
=== FINAL MULTI-CELL TEST === 1. Original map (San Francisco area): Layers: ['sf_poi', 'berkeley_area'] Center: [37.7749, -122.4194] Zoom: 12.0
print("2. Second map (New York City):")
print(" Layers:", list(m2.get_layers().keys()))
print(" Center:", m2.center, "Zoom:", m2.zoom)
m2
2. Second map (New York City): Layers: ['nyc_poi'] Center: [40.7128, -74.006] Zoom: 12.0
print("3. Third map (London - after layer clearing):")
print(" Layers:", list(m3.get_layers().keys()))
print(" Center:", m3.center, "Zoom:", m3.zoom)
m3
3. Third map (London - after layer clearing): Layers: [] Center: [51.5074, -0.1278] Zoom: 12.0
Test Summary¶
If all the above tests worked correctly, you should observe:
- ✅ Multi-cell rendering: The same map instance renders correctly in multiple cells
- ✅ Layer persistence: Layers added to a map persist when the map is rendered in different cells
- ✅ Independent instances: Multiple map instances work independently without interfering with each other
- ✅ Dynamic modifications: Map property changes (zoom, center, bearing, pitch) work correctly
- ✅ Event handling: Click and move events are captured and handled properly
- ✅ Layer management: Adding, removing, and clearing layers works as expected
- ✅ State synchronization: The JavaScript frontend and Python backend maintain synchronized state
Compatibility Notes¶
This implementation should work in:
- JupyterLab (Classic and modern)
- Jupyter Notebook (Classic)
- VS Code with Jupyter extension
- Google Colab
- Other Jupyter-compatible environments
The anywidget framework ensures broad compatibility across different Jupyter environments.