{"id":2523,"date":"2025-12-04T07:00:54","date_gmt":"2025-12-04T13:00:54","guid":{"rendered":"https:\/\/wollen.org\/blog\/?p=2523"},"modified":"2026-02-01T08:14:38","modified_gmt":"2026-02-01T14:14:38","slug":"matterhorn-3d","status":"publish","type":"post","link":"https:\/\/wollen.org\/blog\/2025\/12\/matterhorn-3d\/","title":{"rendered":"The Matterhorn in 3D"},"content":{"rendered":"<p>I&#8217;ve wanted to post a Matplotlib 3D visualization for a while, but sharing it on the web is difficult. It&#8217;s easy to save 2D plots and embed them on this page. For a 3D plot, it works better when you can do <code>plt.show()<\/code> and explore it through the figure window.<\/p>\n<p>I think I found a good subject and a satisfying way to present it. The plan is to do <code>plt.savefig()<\/code> like usual, and then create an animation that rotates 360\u00b0 around the plot.<\/p>\n<p>The <a href=\"https:\/\/en.wikipedia.org\/wiki\/Matterhorn\" target=\"_blank\" rel=\"noopener\">Matterhorn<\/a> is a famous mountain peak in the Alps, just north of the Swiss-Italian border. Its summit rests at 14,692 feet above sea level. That makes it only the 12th-tallest mountain in the Alps, but its four steep faces that tower over the surrounding area make it one of the most recognizable in the world.<\/p>\n<p>The Swiss Federal Office of Topography provides an extremely precise digital elevation model (DEM) of their whole country. I think visualizing the Matterhorn is a perfect way to jump into 3D data with Matplotlib.<\/p>\n<figure id=\"attachment_2528\" aria-describedby=\"caption-attachment-2528\" style=\"width: 300px\" class=\"wp-caption aligncenter\"><a href=\"https:\/\/wollen.org\/blog\/wp-content\/uploads\/2025\/12\/smithsonian_matterhorn.jpg\"><img loading=\"lazy\" decoding=\"async\" class=\"size-medium wp-image-2528\" src=\"https:\/\/wollen.org\/blog\/wp-content\/uploads\/2025\/12\/smithsonian_matterhorn-300x200.jpg\" alt=\"\" width=\"300\" height=\"200\" srcset=\"https:\/\/wollen.org\/blog\/wp-content\/uploads\/2025\/12\/smithsonian_matterhorn-300x200.jpg 300w, https:\/\/wollen.org\/blog\/wp-content\/uploads\/2025\/12\/smithsonian_matterhorn-768x512.jpg 768w, https:\/\/wollen.org\/blog\/wp-content\/uploads\/2025\/12\/smithsonian_matterhorn-600x400.jpg 600w, https:\/\/wollen.org\/blog\/wp-content\/uploads\/2025\/12\/smithsonian_matterhorn.jpg 1000w\" sizes=\"auto, (max-width: 300px) 100vw, 300px\" \/><\/a><figcaption id=\"caption-attachment-2528\" class=\"wp-caption-text\">Matterhorn. Image credit: Smithsonian Magazine.<\/figcaption><\/figure>\n<hr \/>\n<h4>1. Download the data.<\/h4>\n<p>You can access swissALTI3D DEM data <a href=\"https:\/\/www.swisstopo.admin.ch\/en\/height-model-swissalti3d#swissALTI3D---Download\" target=\"_blank\" rel=\"noopener\">here<\/a>.<\/p>\n<p>They provide the DEM in a couple different formats. I like the idea of working with bare-bones <em>XYZ<\/em> files. With these files, you get a bunch of three-dimensional (x, y, z) coordinates, hopefully in a nice rectangular pattern, written in plain text like a <em>CSV<\/em> file. Here&#8217;s a sample of the data:<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\">X Y Z\r\n2615001 1090999 3495.52\r\n2615003 1090999 3495.18\r\n2615005 1090999 3494.36<\/pre>\n<p>Z represents elevation above sea level (meters). X and Y are reported in Switzerland&#8217;s LV95 coordinate system (also in meters). You could convert the data to a more recognizable latitude-longitude system but it doesn&#8217;t seem necessary.<\/p>\n<p>The DEM divides Switzerland into a grid of 1 km\u00b2 sections. I&#8217;ve located the Matterhorn and selected a 3&#215;3 square around the peak. swissALTI3D provides a text file containing URLs for each section&#8217;s data. I&#8217;ll link that file at the bottom of this post.<\/p>\n<hr style=\"width: 50%;\" \/>\n<p>Downloading nine files manually isn&#8217;t that difficult but I would still rather use Python. It will make it easy to grab hundreds of files for a future map.<\/p>\n<p>The project&#8217;s folder structure is shown below. We have two subfolders: one for downloaded <em>ZIP<\/em> files and another for <em>XYZ<\/em> files after they&#8217;ve been decompressed.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\">project_folder\/\r\n\u251c\u2500\u2500 main.py\r\n\u251c\u2500\u2500 zip_files\/\r\n\u2514\u2500\u2500 xyz_files\/<\/pre>\n<p>Use <a href=\"https:\/\/pypi.org\/project\/requests\/\" target=\"_blank\" rel=\"noopener\">requests<\/a> and <a href=\"https:\/\/docs.python.org\/3\/library\/zipfile.html\" target=\"_blank\" rel=\"noopener\">zipfile<\/a> to grab the data. Append file paths to a list so we can reference them later.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"python\">import requests\r\nfrom zipfile import ZipFile\r\nfrom os import listdir\r\nimport pandas as pd\r\n\r\nfile_path_list = []\r\n\r\ndf = pd.read_csv(\"ch.swisstopo.swissalti3d-7Nq9hXI9.csv\", names=[\"url\"])\r\n\r\nfor r, row in df.iterrows():\r\n    response = requests.get(row['url'], stream=True)\r\n    with open(f\"zip_files\/{r}.zip\", \"wb\") as f:\r\n        for chunk in response.iter_content(chunk_size=8192):\r\n            f.write(chunk)\r\n\r\n    with ZipFile(f\"zip_files\/{r}.zip\", 'r') as z:\r\n        z.extractall(f\"xyz_files\/{r}\")\r\n\r\n    file_path_list.append(f\"xyz_files\/{r}\/{listdir(f'xyz_files\/{r}')[0]}\")<\/pre>\n<hr \/>\n<h4>2. Prepare the data.<\/h4>\n<p>I&#8217;m most comfortable with pandas but you could just as easily read the data with NumPy. It will be converted into NumPy arrays anyway so that approach would make sense.<\/p>\n<p>Load the nine <em>XYZ<\/em> files into individual pandas DataFrames and append them to a list. Then call <code>pd.concat<\/code> to stack them into one large DataFrame.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"python\">df_list = []\r\n\r\nfor file in file_path_list:\r\n    df_list.append(pd.read_csv(file, delimiter=\" \"))\r\n\r\ndf = pd.concat(df_list)<\/pre>\n<p>This is optional but I want to convert the Z column (elevation) from meters to feet. Multiply the column by a conversion factor (\u22483.28).<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"python\">df['Z'] = df['Z'] * 3.28084<\/pre>\n<p><code>df.head()<\/code> looks like this:<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\">         X        Y             Z\r\n0  2615001  1090999  11468.241837\r\n1  2615003  1090999  11467.126351\r\n2  2615005  1090999  11464.436062\r\n3  2615007  1090999  11454.527926\r\n4  2615009  1090999  11446.817952<\/pre>\n<p>We&#8217;re looking at a small plot of terrain about 3,000 feet below Matterhorn&#8217;s peak.<\/p>\n<p>Notice that X increments by 2. Each 1 km square is further divided into a 500&#215;500 grid of 2-meter squares. Every row of the DataFrame represents one of those squares. Matplotlib will plot the locations in 3D space to visualize the mountain.<\/p>\n<p>If you do the math, each 1 km square has 500*500=250,000 points. We stacked nine of them together so <code>df<\/code> has 2.25 million rows. That&#8217;s easily the largest dataset I&#8217;ve worked with on the blog. It&#8217;s only ~60 MB so memory shouldn&#8217;t be an issue, but it might take your PC a couple minutes to run the script.<\/p>\n<p>A lot of people say, &#8220;Why bother, I can just use Excel.&#8221; This is a good example of when you definitely <em>can&#8217;t<\/em> do the same thing in Excel.<\/p>\n<hr style=\"width: 50%;\" \/>\n<p>Now we have to convert the data into something Matplotlib can understand.<\/p>\n<p>It wouldn&#8217;t make sense to plot all 2.25 million points on a small 1000-pixel image. It would take significantly longer to process and we simply don&#8217;t have enough screen resolution to see it.<\/p>\n<p>Instead we&#8217;ll decide on a lower number of divisions\u2014maybe 200\u2014and divide the XY area into a grid of larger squares. Then we&#8217;ll use <code>scipy.interpolate.griddata<\/code> to calculate Z values for the new grid map.<\/p>\n<p><code>np.linspace<\/code> creates an evenly spaced array ranging from minimum to maximum values. <code>np.meshgrid<\/code> is something I&#8217;ve written about <a href=\"https:\/\/wollen.org\/blog\/2025\/02\/a-field-guide-to-two-dimensional-arrays\/\">in greater detail<\/a> before. It essentially reshapes a pair of 1D arrays into a pair of 2D arrays with corresponding points. You can think of them like a map of all the grid points.<\/p>\n<p>To visualize terrain, we combine them with another 2D array that holds elevation data, which is where <code>griddata<\/code> comes in. This method accepts the original (x, y, z) data and the new grid arrays.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"python\">import numpy as np\r\nfrom scipy.interpolate import griddata\r\n\r\nx = df['X']\r\ny = df['Y']\r\nz = df['Z']\r\n\r\nx_grid = np.linspace(x.min(), x.max(), 200)\r\ny_grid = np.linspace(y.min(), y.max(), 200)\r\nx_grid, y_grid = np.meshgrid(x_grid, y_grid)\r\n\r\nz_elevation = griddata((x, y), z, (x_grid, y_grid), method=\"cubic\")<\/pre>\n<p>You&#8217;ll notice diminishing returns after 200 divisions. Even this plot won&#8217;t look significantly better than 100 but we can err on the high side.<\/p>\n<p>Here&#8217;s a diagram that shows a 1 km\u00b2 chunk of Switzerland at various resolutions. You can spot finer features with higher resolution, but obviously there&#8217;s a tradeoff with CPU time.<\/p>\n<p><a href=\"https:\/\/wollen.org\/blog\/wp-content\/uploads\/2025\/12\/resolution_comparison.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-2543\" src=\"https:\/\/wollen.org\/blog\/wp-content\/uploads\/2025\/12\/resolution_comparison.png\" alt=\"\" width=\"710\" height=\"500\" srcset=\"https:\/\/wollen.org\/blog\/wp-content\/uploads\/2025\/12\/resolution_comparison.png 710w, https:\/\/wollen.org\/blog\/wp-content\/uploads\/2025\/12\/resolution_comparison-300x211.png 300w\" sizes=\"auto, (max-width: 710px) 100vw, 710px\" \/><\/a><\/p>\n<p>You might think, &#8220;If I set the argument in linspace() to 500, that would be like using all the data in the <em>XYZ<\/em> file. Why wouldn&#8217;t I just generate the Z array myself?&#8221;<\/p>\n<p>You could do that. But you&#8217;d <em>probably<\/em> end up writing a nested <code>for<\/code> loop that&#8217;s significantly slower than <code>scipy.interpolate.griddata<\/code>. If you&#8217;re a wizard then feel free. I know when I&#8217;m outmatched so I&#8217;ll happily rely on SciPy.<\/p>\n<hr \/>\n<h4>3. Plot the data.<\/h4>\n<p>I&#8217;ll stick with default Matplotlib and skip creating a custom mplstyle. I want to write the simplest possible example (within reason) to help someone get started with 3D data.<\/p>\n<p>Instantiating a 3D Axes is a little more awkward than 2D. It&#8217;s best to first create a Figure and <em>then<\/em> create an Axes. &#8220;111&#8221; is a quick way of saying, &#8220;1 row, 1 column, 1st position.&#8221; Use <code>subplots_adjust<\/code> to specify the plot&#8217;s outer margins. Units are relative to the entire figure (0.0 to 1.0).<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"python\">import matplotlib.pyplot as plt\r\n\r\nfig = plt.figure(figsize=(10, 8.4))\r\nax = fig.add_subplot(111, projection=\"3d\")\r\nfig.subplots_adjust(left=0.04, right=0.99, bottom=0.01, top=0.99)<\/pre>\n<p>The important business happens in <code>plot_surface<\/code>. Here we pass the three 2D NumPy arrays we just created.<\/p>\n<p><code>cmap<\/code> refers to color map, which will automatically color the surface according to Z value. In this application Z represents elevation, so we&#8217;ll see a gradient from the base of Matterhorn to its peak. <code>linewidth<\/code> and <code>edgecolor<\/code> give the surface a sort of wire frame that makes it easier to see when rendered on screen. You could omit them but I think they improve the visualization.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"python\">surface = ax.plot_surface(x_grid, y_grid, z_elevation, cmap=\"terrain\", linewidth=0.5, edgecolor=\"#444\")<\/pre>\n<p>Let&#8217;s also create a <code>colorbar<\/code>, which is like a legend for Z values. It tells us exactly what elevation is represented by a given color. By default <code>colorbar<\/code> steals part of its parent&#8217;s Axes, so it will slide in to the surface&#8217;s right.<\/p>\n<p><code>shrink<\/code> is the color bar&#8217;s size relative to its Axes. 0.5 means it will be half the overall Axes height. <code>aspect<\/code> defines height-width ratio, so this color bar will be tall and skinny. There&#8217;s no way to set a color bar title directly but we can call a method on the instance after it&#8217;s created.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"python\">color_bar = fig.colorbar(surface, shrink=0.5, aspect=10)\r\ncolor_bar.set_label(\"Elevation (ft)\")<\/pre>\n<p>Set a title for the plot as well.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"python\">ax.set_title(\"Matterhorn Terrain Map\", size=11)<\/pre>\n<p><code>view_init<\/code> positions the camera. It wouldn&#8217;t be necessary if we were calling <code>plt.show()<\/code>. Once the figure window appeared you could click and drag the camera wherever you wanted. But it&#8217;s very important to locate the camera when producing a static PNG image. We only get one look at the surface.<\/p>\n<p><code>elev<\/code> (elevation) moves the camera up and down. <code>azim<\/code> (azimuth) rotates it like the moon rotates around the Earth. The chosen values get close to matching the Matterhorn picture on its Wikipedia page (shown below).<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"python\">ax.view_init(elev=10, azim=45)<\/pre>\n<p>Finally, save the figure. It might take a minute or two for the script to run, depending on your internet and CPU speeds.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"python\">plt.savefig(\"matterhorn.png\", dpi=200)<\/pre>\n<hr \/>\n<h4>4. The output.<\/h4>\n<p>This shows a 3 km x 3 km area surrounding the Matterhorn. It isn&#8217;t the highest peak in the world but it&#8217;s arguably the most striking. You can see why it&#8217;s so famous.<\/p>\n<p><a href=\"https:\/\/wollen.org\/blog\/wp-content\/uploads\/2025\/12\/matterhorn.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-2537\" src=\"https:\/\/wollen.org\/blog\/wp-content\/uploads\/2025\/12\/matterhorn.png\" alt=\"\" width=\"2000\" height=\"1680\" srcset=\"https:\/\/wollen.org\/blog\/wp-content\/uploads\/2025\/12\/matterhorn.png 2000w, https:\/\/wollen.org\/blog\/wp-content\/uploads\/2025\/12\/matterhorn-300x252.png 300w, https:\/\/wollen.org\/blog\/wp-content\/uploads\/2025\/12\/matterhorn-1024x860.png 1024w, https:\/\/wollen.org\/blog\/wp-content\/uploads\/2025\/12\/matterhorn-768x645.png 768w, https:\/\/wollen.org\/blog\/wp-content\/uploads\/2025\/12\/matterhorn-1536x1290.png 1536w\" sizes=\"auto, (max-width: 2000px) 100vw, 2000px\" \/><\/a><\/p>\n<p>Real life for comparison:<\/p>\n<figure id=\"attachment_2538\" aria-describedby=\"caption-attachment-2538\" style=\"width: 300px\" class=\"wp-caption aligncenter\"><a href=\"https:\/\/wollen.org\/blog\/wp-content\/uploads\/2025\/12\/wikipedia_matterhorn.jpg\"><img loading=\"lazy\" decoding=\"async\" class=\"size-medium wp-image-2538\" src=\"https:\/\/wollen.org\/blog\/wp-content\/uploads\/2025\/12\/wikipedia_matterhorn-300x200.jpg\" alt=\"\" width=\"300\" height=\"200\" srcset=\"https:\/\/wollen.org\/blog\/wp-content\/uploads\/2025\/12\/wikipedia_matterhorn-300x200.jpg 300w, https:\/\/wollen.org\/blog\/wp-content\/uploads\/2025\/12\/wikipedia_matterhorn-768x511.jpg 768w, https:\/\/wollen.org\/blog\/wp-content\/uploads\/2025\/12\/wikipedia_matterhorn-600x400.jpg 600w, https:\/\/wollen.org\/blog\/wp-content\/uploads\/2025\/12\/wikipedia_matterhorn.jpg 1000w\" sizes=\"auto, (max-width: 300px) 100vw, 300px\" \/><\/a><figcaption id=\"caption-attachment-2538\" class=\"wp-caption-text\">Matterhorn. Image credit: Wikipedia.<\/figcaption><\/figure>\n<p>There&#8217;s a lot of empty white space in the output image. It&#8217;s there because when you do <code>plt.show<\/code> and move the camera around, axis labels need a lot of space. There&#8217;s only so much you can do about it natively in Matplotlib. If it bothers you (like it does me), one solution is to use <a href=\"https:\/\/docs.opencv.org\/4.x\/index.html\" target=\"_blank\" rel=\"noopener\">OpenCV<\/a> to crop out white space.<\/p>\n<p>That&#8217;s what I did to create this GIF:<\/p>\n<p><a href=\"https:\/\/wollen.org\/blog\/wp-content\/uploads\/2025\/12\/matterhorn_animated.gif\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-2540\" src=\"https:\/\/wollen.org\/blog\/wp-content\/uploads\/2025\/12\/matterhorn_animated.gif\" alt=\"\" width=\"456\" height=\"275\" \/><\/a><\/p>\n<p>I love how the animation turned out. Mountains are so big that it&#8217;s tough to get a good look around them. The code is essentially the same as above except it generates 180 images while incrementing the azimuth two degrees each frame.<\/p>\n<p>I like this script because it takes the most basic possible three-dimensional spatial data\u2014(x, y, z) coordinates in a <em>CSV<\/em> file\u2014and outputs a nice visualization. You don&#8217;t need a proprietary database format and a whole software package to work with it. Even in 3D, you can get the job done with open-source software.<!-- HFCM by 99 Robots - Snippet # 15: endmark-python -->\n<span class=\"endmark-python\"><\/span>\n<!-- \/end HFCM by 99 Robots -->\n<\/p>\n<hr \/>\n<p><a href=\"https:\/\/wollen.org\/misc\/switzerland_matterhorn_2025.zip\"><strong>Download the data.<\/strong><\/a><\/p>\n<p><strong>Full code:<\/strong><\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"python\">import requests\r\nfrom zipfile import ZipFile\r\nfrom os import listdir\r\nimport pandas as pd\r\nimport numpy as np\r\nfrom scipy.interpolate import griddata\r\nimport matplotlib.pyplot as plt\r\n\r\n\r\nfile_path_list = []\r\n\r\ndf = pd.read_csv(\"ch.swisstopo.swissalti3d-7Nq9hXI9.csv\", names=[\"url\"])\r\n\r\nfor r, row in df.iterrows():\r\n    response = requests.get(row['url'], stream=True)\r\n    with open(f\"zip_files\/{r}.zip\", \"wb\") as f:\r\n        for chunk in response.iter_content(chunk_size=8192):\r\n            f.write(chunk)\r\n    \r\n    with ZipFile(f\"zip_files\/{r}.zip\", 'r') as z:\r\n        z.extractall(f\"xyz_files\/{r}\")\r\n\r\n    file_path_list.append(f\"xyz_files\/{r}\/{listdir(f'xyz_files\/{r}')[0]}\")\r\n\r\ndf_list = []\r\n\r\nfor file in file_path_list:\r\n    df_list.append(pd.read_csv(file, delimiter=\" \"))\r\n\r\ndf = pd.concat(df_list)\r\n\r\ndf['Z'] = df['Z'] * 3.28084\r\n\r\nx = df['X']\r\ny = df['Y']\r\nz = df['Z']\r\n\r\nx_grid = np.linspace(x.min(), x.max(), 200)\r\ny_grid = np.linspace(y.min(), y.max(), 200)\r\nx_grid, y_grid = np.meshgrid(x_grid, y_grid)\r\n\r\nz_elevation = griddata((x, y), z, (x_grid, y_grid), method=\"cubic\")\r\n\r\nfig = plt.figure(figsize=(10, 8.4))\r\nax = fig.add_subplot(111, projection=\"3d\")\r\nfig.subplots_adjust(left=0.04, right=0.99, bottom=0.01, top=0.99)\r\n\r\nsurface = ax.plot_surface(x_grid, y_grid, z_elevation, cmap=\"terrain\", linewidth=0.5, edgecolor=\"#444\")\r\n\r\ncolor_bar = fig.colorbar(surface, shrink=0.5, aspect=10)\r\ncolor_bar.set_label(\"Elevation (ft)\")\r\n\r\nax.set_title(\"Matterhorn Terrain Map\", size=11)\r\n\r\nax.view_init(elev=10, azim=45)\r\n\r\nplt.savefig(\"matterhorn.png\", dpi=200)<\/pre>\n<p>&nbsp;<\/p>\n","protected":false},"excerpt":{"rendered":"<p>I&#8217;ve wanted to post a Matplotlib 3D visualization for a while, but sharing it on the web is difficult. It&#8217;s easy to save 2D plots and embed them on this page. For a 3D plot, it works better when you<\/p>\n","protected":false},"author":1,"featured_media":2552,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[19,561,279],"tags":[507,506,387,517,520,39,515,519,526,278,22,122,518,530,529,438,522,524,72,521,73,271,24,509,388,510,75,514,30,512,505,25,523,513,527,508,511,528,516,141,525],"class_list":["post-2523","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-maps","category-animated","category-nature","tag-2d","tag-3d","tag-animated","tag-azimuth","tag-cmap","tag-code","tag-colorbar","tag-colormap","tag-concat","tag-coordinates","tag-data","tag-dataset","tag-elevation","tag-geography","tag-geology","tag-gif","tag-griddata","tag-interpolate","tag-latitude","tag-linspace","tag-longitude","tag-map","tag-matplotlib","tag-matterhorn","tag-meshgrid","tag-mountain","tag-numpy","tag-opencv","tag-pandas","tag-plot_surface","tag-programming","tag-python","tag-scipy-interpolate-griddata","tag-surface","tag-swissalti3d","tag-switzerland","tag-terrain-map","tag-topography","tag-view_init","tag-visualization","tag-xyz"],"_links":{"self":[{"href":"https:\/\/wollen.org\/blog\/wp-json\/wp\/v2\/posts\/2523","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/wollen.org\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/wollen.org\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/wollen.org\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/wollen.org\/blog\/wp-json\/wp\/v2\/comments?post=2523"}],"version-history":[{"count":34,"href":"https:\/\/wollen.org\/blog\/wp-json\/wp\/v2\/posts\/2523\/revisions"}],"predecessor-version":[{"id":3361,"href":"https:\/\/wollen.org\/blog\/wp-json\/wp\/v2\/posts\/2523\/revisions\/3361"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/wollen.org\/blog\/wp-json\/wp\/v2\/media\/2552"}],"wp:attachment":[{"href":"https:\/\/wollen.org\/blog\/wp-json\/wp\/v2\/media?parent=2523"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/wollen.org\/blog\/wp-json\/wp\/v2\/categories?post=2523"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/wollen.org\/blog\/wp-json\/wp\/v2\/tags?post=2523"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}