{"spec_id":"map-tile-background","library":"letsplot","language":"python","code":"\"\"\" anyplot.ai\nmap-tile-background: Map with Tile Background\nLibrary: letsplot 4.10.1 | Python 3.13.13\nQuality: 91/100 | Updated: 2026-05-27\n\"\"\"\n\nimport os\n\nimport pandas as pd\nfrom lets_plot import (\n    LetsPlot,\n    aes,\n    element_rect,\n    element_text,\n    geom_livemap,\n    geom_point,\n    geom_polygon,\n    geom_rect,\n    ggplot,\n    ggsize,\n    labs,\n    layer_tooltips,\n    scale_size,\n    theme,\n    theme_void,\n    tilesets,\n)\nfrom lets_plot.export import ggsave\n\n\nLetsPlot.setup_html()\n\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\nPAGE_BG = \"#FAF8F1\" if THEME == \"light\" else \"#1A1A17\"\nELEVATED_BG = \"#FFFDF6\" if THEME == \"light\" else \"#242420\"\nINK = \"#1A1A17\" if THEME == \"light\" else \"#F0EFE8\"\nINK_SOFT = \"#4A4A44\" if THEME == \"light\" else \"#B8B7B0\"\nINK_MUTED = \"#6B6A63\" if THEME == \"light\" else \"#A8A79F\"\nBRAND = \"#009E73\"  # Imprint palette position 1 — ALWAYS first series\n\n# Tile and land polygon colors adapt to theme\nTILE_BG = \"#E8E8E6\" if THEME == \"light\" else \"#2A2A27\"\nTILE_BORDER = \"#D0D0CE\" if THEME == \"light\" else \"#3A3A37\"\nLAND_FILL = \"#D5D1C8\" if THEME == \"light\" else \"#38382F\"\nLAND_BORDER = \"#B5B1A4\" if THEME == \"light\" else \"#4A4A40\"\n\n# Data: European cities with annual visitor counts (thousands)\ncities_data = {\n    \"city\": [\n        \"Paris\",\n        \"London\",\n        \"Berlin\",\n        \"Rome\",\n        \"Madrid\",\n        \"Amsterdam\",\n        \"Vienna\",\n        \"Prague\",\n        \"Barcelona\",\n        \"Munich\",\n        \"Brussels\",\n        \"Zurich\",\n        \"Milan\",\n        \"Dublin\",\n        \"Copenhagen\",\n        \"Stockholm\",\n        \"Oslo\",\n        \"Helsinki\",\n        \"Warsaw\",\n        \"Budapest\",\n    ],\n    \"lat\": [\n        48.86,\n        51.51,\n        52.52,\n        41.90,\n        40.42,\n        52.37,\n        48.21,\n        50.08,\n        41.39,\n        48.14,\n        50.85,\n        47.38,\n        45.46,\n        53.35,\n        55.68,\n        59.33,\n        59.91,\n        60.17,\n        52.23,\n        47.50,\n    ],\n    \"lon\": [\n        2.35,\n        -0.13,\n        13.40,\n        12.50,\n        -3.70,\n        4.90,\n        16.37,\n        14.44,\n        2.17,\n        11.58,\n        4.35,\n        8.54,\n        9.19,\n        -6.26,\n        12.57,\n        18.07,\n        10.75,\n        24.94,\n        21.01,\n        19.04,\n    ],\n    \"visitors\": [\n        38000,\n        32000,\n        14000,\n        17000,\n        12000,\n        9000,\n        8000,\n        9500,\n        12000,\n        8500,\n        5500,\n        4000,\n        8000,\n        6000,\n        4500,\n        5000,\n        3500,\n        3000,\n        4000,\n        5500,\n    ],\n}\ndf = pd.DataFrame(cities_data)\n\nTITLE = \"map-tile-background · python · letsplot · anyplot.ai\"\n\n# Interactive HTML version — geom_livemap with real tile provider\nmap_tiles = tilesets.LETS_PLOT_DARK if THEME == \"dark\" else tilesets.CARTO_POSITRON\n\nplot_interactive = (\n    ggplot()\n    + geom_livemap(location=[-12, 35, 32, 72], zoom=4, tiles=map_tiles)\n    + geom_point(\n        aes(x=\"lon\", y=\"lat\", size=\"visitors\"),\n        data=df,\n        fill=BRAND,\n        color=PAGE_BG,\n        alpha=0.85,\n        shape=21,\n        stroke=2,\n        tooltips=layer_tooltips().title(\"@city\").line(\"Visitors|@visitors K/year\"),\n    )\n    + scale_size(range=[6, 22], name=\"Visitors (thousands)\")\n    + labs(title=TITLE)\n    + ggsize(800, 450)\n    + theme(\n        plot_title=element_text(size=16, color=INK),\n        legend_title=element_text(size=10, color=INK),\n        legend_text=element_text(size=10, color=INK_SOFT),\n        legend_background=element_rect(fill=ELEVATED_BG),\n        plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG),\n        legend_position=\"right\",\n    )\n)\n\nggsave(plot_interactive, f\"plot-{THEME}.html\", path=\".\")\n\n# Static PNG version — simulated tile-style background for raster export\ntiles_rows = []\ntile_size = 3\nfor lon_val in range(-15, 35, tile_size):\n    for lat_val in range(35, 75, tile_size):\n        tiles_rows.append({\"xmin\": lon_val, \"xmax\": lon_val + tile_size, \"ymin\": lat_val, \"ymax\": lat_val + tile_size})\ndf_tiles = pd.DataFrame(tiles_rows)\n\n# European landmass polygons — per-country outlines for recognizable geography\n# France (includes Breton peninsula; ~29 vertices)\nfrance = pd.DataFrame(\n    {\n        \"lon\": [\n            -1.8,\n            -2.1,\n            -2.0,\n            -1.5,\n            -2.3,\n            -2.5,\n            -4.5,\n            -4.8,\n            -3.8,\n            -2.5,\n            -1.8,\n            -1.5,\n            0.0,\n            -1.0,\n            0.8,\n            2.5,\n            3.0,\n            4.0,\n            5.5,\n            6.3,\n            7.7,\n            7.5,\n            7.0,\n            7.0,\n            5.0,\n            4.2,\n            3.0,\n            1.5,\n            -1.8,\n        ],\n        \"lat\": [\n            43.4,\n            44.0,\n            45.5,\n            46.5,\n            47.3,\n            48.4,\n            48.4,\n            48.1,\n            47.5,\n            47.8,\n            47.1,\n            47.0,\n            48.0,\n            49.5,\n            49.8,\n            51.0,\n            50.3,\n            49.8,\n            49.5,\n            49.5,\n            47.5,\n            47.4,\n            45.9,\n            43.7,\n            43.3,\n            43.2,\n            42.5,\n            43.3,\n            43.4,\n        ],\n        \"region\": [\"France\"] * 29,\n    }\n)\n\n# Iberian Peninsula — Spain + Portugal (~21 vertices)\niberia = pd.DataFrame(\n    {\n        \"lon\": [\n            -9.2,\n            -7.5,\n            -4.5,\n            -1.8,\n            3.2,\n            3.3,\n            1.8,\n            0.5,\n            -0.2,\n            -0.5,\n            -1.5,\n            -2.5,\n            -4.5,\n            -5.5,\n            -6.5,\n            -7.5,\n            -8.8,\n            -9.2,\n            -9.5,\n            -9.5,\n            -9.2,\n        ],\n        \"lat\": [\n            43.8,\n            43.7,\n            43.5,\n            43.4,\n            42.5,\n            41.5,\n            40.5,\n            39.5,\n            38.0,\n            37.5,\n            36.7,\n            36.7,\n            36.5,\n            36.2,\n            37.0,\n            37.0,\n            37.0,\n            37.0,\n            38.5,\n            41.0,\n            43.8,\n        ],\n        \"region\": [\"Iberia\"] * 21,\n    }\n)\n\n# Central Europe — Germany, Netherlands, Belgium, Austria, Czech, Slovakia (~24 vertices)\ncentral_europe = pd.DataFrame(\n    {\n        \"lon\": [\n            6.3,\n            7.7,\n            8.0,\n            10.0,\n            13.0,\n            15.5,\n            16.5,\n            17.0,\n            18.5,\n            18.0,\n            15.0,\n            14.5,\n            13.5,\n            10.0,\n            9.0,\n            8.5,\n            7.0,\n            5.5,\n            3.5,\n            3.5,\n            3.0,\n            4.5,\n            5.8,\n            6.3,\n        ],\n        \"lat\": [\n            49.5,\n            47.5,\n            47.7,\n            47.5,\n            47.7,\n            48.5,\n            48.8,\n            48.5,\n            49.5,\n            50.5,\n            51.0,\n            53.0,\n            54.5,\n            54.8,\n            55.0,\n            54.8,\n            53.5,\n            53.5,\n            53.0,\n            51.5,\n            51.0,\n            50.5,\n            50.5,\n            49.5,\n        ],\n        \"region\": [\"Central_EU\"] * 24,\n    }\n)\n\n# Eastern Europe — Poland, Balkans, Romania, Hungary, Ukraine west (~18 vertices)\neastern_europe = pd.DataFrame(\n    {\n        \"lon\": [\n            18.5,\n            18.0,\n            15.0,\n            14.5,\n            18.5,\n            20.0,\n            22.0,\n            24.0,\n            26.0,\n            28.0,\n            29.5,\n            30.0,\n            28.0,\n            25.0,\n            22.0,\n            20.0,\n            18.5,\n            18.5,\n        ],\n        \"lat\": [\n            49.5,\n            50.5,\n            51.0,\n            53.0,\n            54.5,\n            54.5,\n            55.0,\n            56.5,\n            57.5,\n            58.0,\n            57.0,\n            55.0,\n            52.0,\n            48.0,\n            44.5,\n            44.0,\n            45.5,\n            49.5,\n        ],\n        \"region\": [\"Eastern_EU\"] * 18,\n    }\n)\n\n# Scandinavia — Norway + Sweden peninsula (~25 vertices)\nscandinavia = pd.DataFrame(\n    {\n        \"lon\": [\n            5.0,\n            8.0,\n            10.0,\n            11.0,\n            12.5,\n            14.0,\n            16.0,\n            18.0,\n            20.0,\n            22.0,\n            25.0,\n            28.0,\n            30.0,\n            28.5,\n            25.0,\n            22.0,\n            19.0,\n            17.5,\n            14.0,\n            11.5,\n            10.5,\n            8.0,\n            5.0,\n            4.5,\n            5.0,\n        ],\n        \"lat\": [\n            58.0,\n            58.0,\n            59.0,\n            58.8,\n            57.5,\n            56.5,\n            56.5,\n            59.0,\n            60.5,\n            62.0,\n            65.0,\n            68.5,\n            70.5,\n            70.5,\n            70.0,\n            68.5,\n            68.0,\n            67.5,\n            65.0,\n            63.0,\n            60.5,\n            58.5,\n            57.5,\n            57.8,\n            58.0,\n        ],\n        \"region\": [\"Scandinavia\"] * 25,\n    }\n)\n\n# Great Britain (~20 vertices)\nbritain = pd.DataFrame(\n    {\n        \"lon\": [\n            -6.0,\n            -5.0,\n            -4.0,\n            -3.0,\n            -2.0,\n            -1.0,\n            0.0,\n            1.5,\n            1.8,\n            0.5,\n            -0.5,\n            -1.5,\n            -3.0,\n            -4.0,\n            -5.0,\n            -5.5,\n            -6.0,\n            -5.0,\n            -4.0,\n            -6.0,\n        ],\n        \"lat\": [\n            50.0,\n            50.0,\n            51.0,\n            51.5,\n            52.0,\n            53.0,\n            53.5,\n            55.0,\n            56.0,\n            57.5,\n            58.5,\n            58.8,\n            58.5,\n            57.0,\n            55.5,\n            53.5,\n            52.0,\n            51.5,\n            50.5,\n            50.0,\n        ],\n        \"region\": [\"Britain\"] * 20,\n    }\n)\n\n# Ireland (~10 vertices)\nireland = pd.DataFrame(\n    {\n        \"lon\": [-10.0, -9.5, -7.5, -6.0, -6.0, -7.0, -8.5, -10.0, -10.5, -10.0],\n        \"lat\": [52.0, 53.5, 55.0, 54.5, 52.5, 51.5, 51.5, 52.0, 53.0, 52.0],\n        \"region\": [\"Ireland\"] * 10,\n    }\n)\n\n# Italy — boot shape (~26 vertices)\nitaly = pd.DataFrame(\n    {\n        \"lon\": [\n            7.0,\n            7.5,\n            9.5,\n            11.0,\n            12.0,\n            13.5,\n            14.5,\n            15.0,\n            15.5,\n            16.0,\n            16.5,\n            18.5,\n            18.5,\n            17.0,\n            16.0,\n            15.0,\n            14.0,\n            13.5,\n            12.5,\n            12.0,\n            11.0,\n            10.0,\n            9.0,\n            8.0,\n            7.0,\n            7.0,\n        ],\n        \"lat\": [\n            43.7,\n            44.0,\n            44.5,\n            44.2,\n            44.3,\n            43.5,\n            42.0,\n            40.5,\n            38.5,\n            37.5,\n            38.0,\n            40.0,\n            41.0,\n            41.5,\n            41.5,\n            42.0,\n            41.5,\n            42.5,\n            42.0,\n            41.5,\n            42.5,\n            43.5,\n            44.2,\n            44.0,\n            43.7,\n            43.7,\n        ],\n        \"region\": [\"Italy\"] * 26,\n    }\n)\n\n# Denmark (~8 vertices)\ndenmark = pd.DataFrame(\n    {\n        \"lon\": [8.0, 9.5, 10.5, 12.5, 12.0, 10.0, 8.5, 8.0],\n        \"lat\": [55.0, 55.0, 57.5, 56.0, 55.5, 57.5, 57.0, 55.0],\n        \"region\": [\"Denmark\"] * 8,\n    }\n)\n\ndf_land = pd.concat(\n    [france, iberia, central_europe, eastern_europe, scandinavia, britain, ireland, italy, denmark], ignore_index=True\n)\n\nplot_static = (\n    ggplot()\n    + geom_rect(\n        aes(xmin=\"xmin\", xmax=\"xmax\", ymin=\"ymin\", ymax=\"ymax\"),\n        data=df_tiles,\n        fill=TILE_BG,\n        color=TILE_BORDER,\n        size=0.2,\n        alpha=0.9,\n    )\n    + geom_polygon(\n        aes(x=\"lon\", y=\"lat\", group=\"region\"), data=df_land, fill=LAND_FILL, color=LAND_BORDER, size=0.6, alpha=0.95\n    )\n    + geom_point(\n        aes(x=\"lon\", y=\"lat\", size=\"visitors\"), data=df, fill=BRAND, color=PAGE_BG, alpha=0.85, shape=21, stroke=2\n    )\n    + scale_size(range=[6, 22], name=\"Visitors (thousands)\")\n    + labs(title=TITLE, caption=\"Tile-style basemap (CARTO Positron style) | © OpenStreetMap contributors\")\n    + ggsize(800, 450)\n    + theme_void()\n    + theme(\n        plot_title=element_text(size=16, color=INK),\n        plot_caption=element_text(size=10, color=INK_MUTED),\n        legend_title=element_text(size=10, color=INK),\n        legend_text=element_text(size=10, color=INK_SOFT),\n        legend_background=element_rect(fill=ELEVATED_BG, color=INK_SOFT),\n        legend_position=\"right\",\n        plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG),\n    )\n)\n\nggsave(plot_static, f\"plot-{THEME}.png\", path=\".\", scale=4)\n"}