{"id":1066,"date":"2023-01-18T16:41:53","date_gmt":"2023-01-18T16:41:53","guid":{"rendered":"https:\/\/ml-gis-service.com\/?p=1066"},"modified":"2023-01-18T16:46:33","modified_gmt":"2023-01-18T16:46:33","slug":"create-travel-map-with-python-and-pygmt","status":"publish","type":"post","link":"https:\/\/ml-gis-service.com\/index.php\/2023\/01\/18\/create-travel-map-with-python-and-pygmt\/","title":{"rendered":"Create Travel Map with Python and PyGMT"},"content":{"rendered":"\n<p>I spent the last year in Finland, but in reality, I was traveling between Finland and the southern part of Poland. The longest distance was about 1600 km by car. At the end of 2022, I have prepared the infographic for my Instagram account with points where I have been for more than one hour along the route:<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-large is-resized\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/ml-gis-service.com\/wp-content\/uploads\/2023\/01\/2022-1024x1024.png\" alt=\"The spherical projections on Earth with Eastern and Northern Europe. Map shows countries boundaries and points where author spent more than one hour (mostly places where he lived).\" class=\"wp-image-1069\" width=\"1024\" height=\"1024\" srcset=\"https:\/\/ml-gis-service.com\/wp-content\/uploads\/2023\/01\/2022-1024x1024.png 1024w, https:\/\/ml-gis-service.com\/wp-content\/uploads\/2023\/01\/2022-300x300.png 300w, https:\/\/ml-gis-service.com\/wp-content\/uploads\/2023\/01\/2022-150x150.png 150w, https:\/\/ml-gis-service.com\/wp-content\/uploads\/2023\/01\/2022-768x768.png 768w, https:\/\/ml-gis-service.com\/wp-content\/uploads\/2023\/01\/2022-1536x1536.png 1536w, https:\/\/ml-gis-service.com\/wp-content\/uploads\/2023\/01\/2022-550x550.png 550w, https:\/\/ml-gis-service.com\/wp-content\/uploads\/2023\/01\/2022-1125x1125.png 1125w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><figcaption class=\"wp-element-caption\">The map of some car travels.<\/figcaption><\/figure><\/div>\n\n\n<p>Points color has its meaning:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>green points &#8211; places of living,<\/li>\n\n\n\n<li>violet points &#8211; sightseeing,<\/li>\n\n\n\n<li>yellow points &#8211; resting places.<\/li>\n<\/ul>\n\n\n\n<p>The mapping wasn&#8217;t straightforward. There were two problems:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>the canvas width-to-height ratio should be as close to one as possible, because of the square posts on Instagram,<\/li>\n\n\n\n<li>the points are dispersed primarily on the south-north axis, making it challenging to produce a near-square map.<\/li>\n<\/ul>\n\n\n\n<p>Using GeoPandas for this task&#8230; is not the best idea. There are better options, for example, the <strong><a href=\"https:\/\/www.pygmt.org\/latest\/\">pygmt<\/a> <\/strong>mapping package. I have written about it once in the article <a href=\"https:\/\/ml-gis-service.com\/index.php\/2022\/01\/06\/data-science-leave-geopandas-and-create-beautiful-map-with-pygmt\/\" data-type=\"post\" data-id=\"607\">Leave GeoPandas and Create Beautiful Map with PyGMT<\/a>. We will go step-by-step through how to create this map. You may check Jupyter Notebook and dataset in the repository here: <a href=\"https:\/\/github.com\/SimonMolinsky\/articles\/tree\/main\/2023-01\/notebooks\">Github<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Installation<\/h2>\n\n\n\n<p>First, you must have <strong>Python<\/strong> and preferably <strong>conda<\/strong> installed in your system. You may consider using Google Colab or a similar cloud service if you wish so. Then you need to install only two packages (three if we count notebook in the local environment):<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>pandas<\/code><\/li>\n\n\n\n<li><code>pygmt<\/code><\/li>\n\n\n\n<li>(optional) <code>notebook<\/code><\/li>\n<\/ul>\n\n\n\n<p>To install those packages with conda run in the terminal:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">conda create -n map python=\"3.10\"<\/pre>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">conda activate map<\/pre>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">conda install -c conda-forge pandas pygmt notebook<\/pre>\n\n\n\n<p>You may install them with <code>pip<\/code> too, but remember to create a virtual environment for it.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Data<\/h2>\n\n\n\n<p>You should have a dataset with point coordinates. My dataset is a simple CSV file, here&#8217;s a sample from it:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><tbody><tr><td><strong>IDX<\/strong><\/td><td><strong>Place<\/strong><\/td><td><strong>Country<\/strong><\/td><td><strong>Reason<\/strong><\/td><td><strong>Latitude<\/strong><\/td><td><strong>Longitude<\/strong><\/td><\/tr><tr><td>0<\/td><td>Lomza<\/td><td>PL<\/td><td>Travel<\/td><td>53.176389<\/td><td>22.073056<\/td><\/tr><tr><td>1<\/td><td>Vantaa<\/td><td>FIN<\/td><td>Living<\/td><td>60.294444<\/td><td>25.040278<\/td><\/tr><\/tbody><\/table><figcaption class=\"wp-element-caption\">Sample data<\/figcaption><\/figure>\n\n\n\n<p>The most important columns are:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Latitude and Longitude: to place our points on the map,<\/li>\n\n\n\n<li>Reason: to color points according to a category from this column.<\/li>\n<\/ul>\n\n\n\n<p>Data is stored in CSV, the file is available in the <a href=\"https:\/\/github.com\/SimonMolinsky\/articles\/tree\/main\/2023-01\/notebooks\">article&#8217;s repository<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Coding &amp; Mapping step by step<\/h2>\n\n\n\n<p>We have tools and materials for work, then let&#8217;s start coding! Open notebook and import core packages:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"python\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">import pygmt\nimport pandas as pd\n<\/pre>\n\n\n\n<p>With <code>pandas<\/code> we can load a dataset from a CSV file:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"python\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">df = pd.read_csv('data_locs.csv', sep=';')\n<\/pre>\n\n\n\n<p>Filename (<code>data_locs.csv<\/code>) is a path to the file I defined, but you must change it if you have a different file to work with. Maybe you&#8217;ve realized that additional argument <code>sep<\/code> (separator) with a semicolon symbol is passed. It is an artifact from my dataset created in the Polish version of Excel, where commas are used to separate decimal numbers. In the USA, and every programming language, you use a dot to describe a half like that: 0.5, but in Polish and a few European languages that I know, a comma is used instead, and a half is written like that: <strong>0,5<\/strong>. That&#8217;s why CSV files cannot be comma-separated, but semicolon-separated or tab-separated. It is not important for our analysis, I&#8217;m pointing it out to clarify this &#8211; possibly &#8211; strange separator.<\/p>\n\n\n\n<p>We have data prepared, thus we can build a canvas for analysis. First, we should define the boundaries of a region of interest:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"python\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">region = [\n    df.Longitude.min() - 1,\n    df.Longitude.max() + 1,\n    df.Latitude.min() - 1,\n    df.Latitude.max() + 1,\n]\n<\/pre>\n\n\n\n<p>And we can jump into plotting:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"python\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">fig = pygmt.Figure()\nfig.basemap(region=region, projection=\"M15c\", frame=True)\nfig.coast(land=\"black\", water=\"skyblue\")\nfig.plot(x=df.Longitude, y=df.Latitude, style=\"c0.3c\", fill=\"white\", pen=\"black\")\nfig.show()\n<\/pre>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-large is-resized\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/ml-gis-service.com\/wp-content\/uploads\/2023\/01\/map1plot-526x1024.png\" alt=\"The map of Poland, Lithuania, Latvia, Estonia, Finland and Baltic Sea with points showing were author traveled.\" class=\"wp-image-1072\" width=\"395\" height=\"768\" srcset=\"https:\/\/ml-gis-service.com\/wp-content\/uploads\/2023\/01\/map1plot-526x1024.png 526w, https:\/\/ml-gis-service.com\/wp-content\/uploads\/2023\/01\/map1plot-154x300.png 154w, https:\/\/ml-gis-service.com\/wp-content\/uploads\/2023\/01\/map1plot-768x1495.png 768w, https:\/\/ml-gis-service.com\/wp-content\/uploads\/2023\/01\/map1plot-789x1536.png 789w, https:\/\/ml-gis-service.com\/wp-content\/uploads\/2023\/01\/map1plot-1052x2048.png 1052w\" sizes=\"auto, (max-width: 395px) 100vw, 395px\" \/><figcaption class=\"wp-element-caption\">The first map is on a planar projection.<\/figcaption><\/figure><\/div>\n\n\n<p>And it is not something that we expected! The map is on planar projection and will be distorted if we try to shrink it along the North-South axis. The other problem is that our points are no different from each other, but we have prepared categories in the <code>Reason<\/code> column that we might use. We start from the second problem and return to squaring a rectangle in the next step.<\/p>\n\n\n\n<p>To paint points according to a category of the <code>Reason<\/code> we must change this column type and perform an operation known as <strong><em>label encoding<\/em><\/strong>. We can assign integer values to categories from categorical columns, it is a valuable property for machine learning purposes but also for data visualization. It can be done with two lines of code in <code>pandas<\/code>:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"python\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">df['Reason'] = pd.Categorical(df['Reason'])\ndf['Val'] = df['Reason'].cat.codes\n<\/pre>\n\n\n\n<p>And with that, we can make our map more interesting:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"python\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">fig = pygmt.Figure()\nfig.basemap(region=region, projection=\"M15c\", frame=True)\nfig.coast(land=\"black\", water=\"skyblue\")\npygmt.makecpt(cmap=\"plasma\", series=[df['Val'].min(), df['Val'].max()])\nfig.plot(\n    x=df.Longitude,\n    y=df.Latitude,\n    fill=df['Val'],\n    cmap=True,\n    style=\"c0.5c\",\n    pen=\"black\",\n)\nfig.show()\n<\/pre>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-large is-resized\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/ml-gis-service.com\/wp-content\/uploads\/2023\/01\/map2plot-526x1024.png\" alt=\"The map of Poland, Lithuania, Latvia, Estonia, Finland and Baltic Sea with points showing were author traveled, but now points have different colors.\" class=\"wp-image-1073\" width=\"395\" height=\"768\" srcset=\"https:\/\/ml-gis-service.com\/wp-content\/uploads\/2023\/01\/map2plot-526x1024.png 526w, https:\/\/ml-gis-service.com\/wp-content\/uploads\/2023\/01\/map2plot-154x300.png 154w, https:\/\/ml-gis-service.com\/wp-content\/uploads\/2023\/01\/map2plot-768x1495.png 768w, https:\/\/ml-gis-service.com\/wp-content\/uploads\/2023\/01\/map2plot-789x1536.png 789w, https:\/\/ml-gis-service.com\/wp-content\/uploads\/2023\/01\/map2plot-1052x2048.png 1052w\" sizes=\"auto, (max-width: 395px) 100vw, 395px\" \/><figcaption class=\"wp-element-caption\">The second map is useful for analytics but not for Instagram posting.<\/figcaption><\/figure><\/div>\n\n\n<p>The second map looks more decent. Decisions or analyses could be made based on the point patterns and their classes. But it isn&#8217;t a map that might be posted on social media. The problem of the width-to-height ratio still exists. The solution is to change the projection from planar to spherical and the angle of view! After a short time, we can create an entirely different map:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"python\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">fig = pygmt.Figure()\nfig.basemap(region=region, projection=\"G12\/55.5\/22c+a90+t40+v60\/60+w0+z1000\", frame=True)\nfig.coast(land=\"#F5F5F5\", water=\"#82CAFA\", borders='1\/1p')\npygmt.makecpt(cmap=\"viridis\", series=[df['Val'].min(), df['Val'].max()])\nfig.plot(\n    x=df.Longitude,\n    y=df.Latitude,\n    fill=df['Val'],\n    cmap=True,\n    style=\"c0.5c\",\n    pen=\"black\",\n)\n# fig.savefig('our_travels_2022.png')\nfig.show()\n<\/pre>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-large is-resized\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/ml-gis-service.com\/wp-content\/uploads\/2023\/01\/map3plt-1024x729.png\" alt=\"Transformed map with a spherical view on a Baltic Sea region. Points have different colors. Map dimensions ratio is closer to one.\" class=\"wp-image-1074\" width=\"768\" height=\"547\" srcset=\"https:\/\/ml-gis-service.com\/wp-content\/uploads\/2023\/01\/map3plt-1024x729.png 1024w, https:\/\/ml-gis-service.com\/wp-content\/uploads\/2023\/01\/map3plt-300x214.png 300w, https:\/\/ml-gis-service.com\/wp-content\/uploads\/2023\/01\/map3plt-768x547.png 768w, https:\/\/ml-gis-service.com\/wp-content\/uploads\/2023\/01\/map3plt-1536x1094.png 1536w\" sizes=\"auto, (max-width: 768px) 100vw, 768px\" \/><figcaption class=\"wp-element-caption\">The final and desired map.<\/figcaption><\/figure><\/div>\n\n\n<p>And with this map, we can go posting on social media.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Do you want to know more?<\/h2>\n\n\n\n<p>Here are additional resources that you might use to understand better what happened with the map:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>For the first map, the basic plots in pygmt: <a href=\"https:\/\/www.pygmt.org\/latest\/tutorials\/basics\/plot.html#sphx-glr-tutorials-basics-plot-py\">https:\/\/www.pygmt.org\/latest\/tutorials\/basics\/plot.html#sphx-glr-tutorials-basics-plot-py<\/a><\/li>\n\n\n\n<li>For the second map, coloring the points: <a href=\"https:\/\/www.pygmt.org\/latest\/tutorials\/basics\/plot.html#sphx-glr-tutorials-basics-plot-py\">https:\/\/www.pygmt.org\/latest\/tutorials\/basics\/plot.html#sphx-glr-tutorials-basics-plot-py<\/a><\/li>\n\n\n\n<li>For the third map, the projection that has been applied: <a href=\"https:\/\/www.pygmt.org\/latest\/projections\/azim\/azim_general_perspective.html#sphx-glr-projections-azim-azim-general-perspective-py\">https:\/\/www.pygmt.org\/latest\/projections\/azim\/azim_general_perspective.html#sphx-glr-projections-azim-azim-general-perspective-py<\/a><\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">And finally, why by car instead of a plane?<\/h2>\n\n\n\n<p>Here&#8217;s why:<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"768\" src=\"https:\/\/ml-gis-service.com\/wp-content\/uploads\/2023\/01\/IMG_2470-kopia-1024x768.jpg\" alt=\"A Saint Bernard dog photo\" class=\"wp-image-1080\" srcset=\"https:\/\/ml-gis-service.com\/wp-content\/uploads\/2023\/01\/IMG_2470-kopia-1024x768.jpg 1024w, https:\/\/ml-gis-service.com\/wp-content\/uploads\/2023\/01\/IMG_2470-kopia-300x225.jpg 300w, https:\/\/ml-gis-service.com\/wp-content\/uploads\/2023\/01\/IMG_2470-kopia-768x576.jpg 768w, https:\/\/ml-gis-service.com\/wp-content\/uploads\/2023\/01\/IMG_2470-kopia-800x600.jpg 800w, https:\/\/ml-gis-service.com\/wp-content\/uploads\/2023\/01\/IMG_2470-kopia.jpg 1200w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><figcaption class=\"wp-element-caption\">This guy can travel only by car, they won&#8217;t take him on a plane!<\/figcaption><\/figure><\/div>","protected":false},"excerpt":{"rendered":"<p>Prepare map for Instagram post<\/p>\n","protected":false},"author":1,"featured_media":1068,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[138,18,137,3],"tags":[139,239,237,141,7,238],"class_list":["post-1066","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-cartography","category-data-science","category-data-visualization","category-python","tag-cartography","tag-journey","tag-mapping","tag-pygmt","tag-python","tag-travel"],"_links":{"self":[{"href":"https:\/\/ml-gis-service.com\/index.php\/wp-json\/wp\/v2\/posts\/1066","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/ml-gis-service.com\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/ml-gis-service.com\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/ml-gis-service.com\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/ml-gis-service.com\/index.php\/wp-json\/wp\/v2\/comments?post=1066"}],"version-history":[{"count":12,"href":"https:\/\/ml-gis-service.com\/index.php\/wp-json\/wp\/v2\/posts\/1066\/revisions"}],"predecessor-version":[{"id":1084,"href":"https:\/\/ml-gis-service.com\/index.php\/wp-json\/wp\/v2\/posts\/1066\/revisions\/1084"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/ml-gis-service.com\/index.php\/wp-json\/wp\/v2\/media\/1068"}],"wp:attachment":[{"href":"https:\/\/ml-gis-service.com\/index.php\/wp-json\/wp\/v2\/media?parent=1066"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/ml-gis-service.com\/index.php\/wp-json\/wp\/v2\/categories?post=1066"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/ml-gis-service.com\/index.php\/wp-json\/wp\/v2\/tags?post=1066"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}