One place for hosting & domains

      How to Create a GIS Application using Flask, Stadia Maps, and MongoDB


      Updated by Linode Contributed by Leslie Salazar

      Geographic Information system (GIS) based applications require a beautiful mapping experience for users. Stadia Maps provides digital mapping that you can easily and affordably integrate into your web or mobile applications. They offer hosted map tiles, offline map tiles, static maps, and a few other core products. If you would like to test their services, you can use a local development environment along with their free tier plan. For more details on pricing and service limits, see their pricing plans.

      In this Guide

      This guide will use Stadia Maps free tier plan and hosted vector map tiles to create a GIS web app using Flask. Your Flask application will use a MongoDB database to store GeoJSON data to display as markers on your Stadia Map powered map. GeoJson is a format for encoding a variety of geographic data structures based on JavaScript Object Notation (JSON).

      The GeoJSON data that you will use is a subset of Philadelphia’s Street Tree Inventory dataset. Since the entire dataset is very large, a subset was used to keep this example simple and to reduce MongoDB storage requirements. When you are finished with this guide, you will have a Stadia Maps powered map with markers displaying the location of the trees that surround Linode’s headquarters in Philadelphia, USA.

      While the example in this guide is simple, its components can be adopted to build a GIS app that maps any data you would like to display to your users.

      The sections in this guide will cover the following topics:

      Before you Begin

      Note

      This guide was written using Python version 3.7.

      1. You can optionally create an account with Stadia maps. When developing locally, you are not required to create an account with Stadia Maps. Once you are ready to deploy your app, you will be required to sign up and select an appropriate service plan.

      2. Install MongoDB following the link’s instructions. This installation will also give you access to the mongoimport command line tool.

      3. We recommend Installing Conda, an open source package and environment management system. Conda lets you easily switch between development environments that have different Python versions and packages installed. While this isn’t a strict requirement for setting up your GIS application, it is a great way to isolate your development environment and keep your system’s global Python version and packages untouched.

      Setup Your Development Environment

      In this section, you will prepare your development environment by creating your project directories, activating your conda environment, installing the required Python packages, and importing your GeoJSON data to your MongoDB database.

      1. Create a project folder to store your Flask app files and move into that folder:

        mkdir ~/stadia-maps && cd ~/stadia-maps
        
      2. If you are using conda, create a new environment running Python 3.7:

        conda create --name py37 python=3.7
        
      3. Activate your new conda environment:

        conda activate py37
        
      4. Install pip if it is not already available with your Python installation. A Python 3 installation (>=3.4) should automatically include pip. Follow the instructions in pip’s official documentation to install pip, if needed.

      5. Install the required Python packages:

        pip install flask flask-session geojson pymongo Flask-PyMongo
        

      Import your GeoJSON File to MongoDB

      Before creating your Flask App, you will set up your MongoDB database to store the example data set. This data set was created from the Philadelphia Street Tree inventory GeoJSON data set.

      1. Run your local MongoDB instance. The instance will need to run so that your Flask app can connect to your project’s database. Follow the steps in MongoDB’s official documentation. These steps vary depending on your computer’s operating system.

      2. In your stadia-maps project directory, create a file named linodeStreetTrees.geojson to store your GeoJSON data. Using the text editor of your choice, copy and paste the data located in this linked file. Your file’s final locations should be ~/stadia-maps/linodeStreetTrees.geojson. You will use the data stored in your local file in the next step.

      3. Open a new terminal window and use the mongoimport command line tool to import your GeoJSON data to your database. The import will create a database and collection named linodeStreetTrees and will use the data stored in the linodeStreetTrees.geojson file to create your collection’s documents. In MongoDB, databases hold collections of documents. Collections are analogous to tables in relational databases. Documents store data records of field-value pairs in BSON format, a binary representation of JSON.

        mongoimport --db linodeStreetTrees --collection linodeStreetTrees --file ~/stadia-maps/linodeStreetTrees.geojson
        
      4. Connect to your MongoDB database to verify that all the data was imported as expected. By default, MongoDB will use port 27017 for database connections.

        mongo mongodb://localhost:27017/linodeStreetTrees
        

        From the MongoDB prompt, use the find() collection method to query your database.

        db.linodeStreetTrees.find()
        

        You should see all the data from your imported GeoJSON file returned.

          
        { "_id" : ObjectId("5e2a0c20b4d6fb3be09261c0"), "type" : "FeatureCollection", "features" : [ { "type" : "Feature", "properties" : { "OBJECTID" : 36391, "SPECIES" : null, "STATUS" : null, "DBH" : null }, "geometry" : { "type" : "Point", "coordinates" : [ -75.14457809997165, 39.95216128175947 ] } }, { "type" : "Feature", "properties" : { "OBJECTID" : 36392, "SPECIES" : null, "STATUS" : null, "DBH" : null }, "geometry" : { "type" : "Point", "coordinates" : [ -75.14446882894926, 39.95213340459382 ] } }, { "type" : "Feature", "properties" : { "OBJECTID" : 36388, "SPECIES" : null, "STATUS" : null, "DBH" : null }, "geometry" : { "type" : "Point", "coordinates" : [ -75.14436203227342, 39.95213350981046 ] } }, { "type" : "Feature", "properties" : { "OBJECTID" : 36387, "SPECIES" : null, "STATUS" : null, "DBH" : null }, "geometry" : { "type" : "Point", "coordinates" : [ -75.14391916318196, 39.95207850652248 ] } }, { "type" : "Feature", "properties" : { "OBJECTID" : 36385, "SPECIES" : null, "STATUS" : null, "DBH" : null }, "geometry" : { "type" : "Point", "coordinates" : [ -75.14371036845057, 39.95207848246513 ] } }, { "type" : "Feature", "properties" : { "OBJECTID" : 34552, "SPECIES" : null, "STATUS" : null, "DBH" : null }, "geometry" : { "type" : "Point", "coordinates" : [ -75.14525481375121, 39.952402827669786 ] } }, { "type" : "Feature", "properties" : { "OBJECTID" : 34553, "SPECIES" : null, "STATUS" : null, "DBH" : null }, "geometry" : { "type" : "Point", "coordinates" : [ -75.14522060144313, 39.95249257200717 ] } }, { "type" : "Feature", "properties" : { "OBJECTID" : 34554, "SPECIES" : null, "STATUS" : null, "DBH" : null }, "geometry" : { "type" : "Point", "coordinates" : [ -75.14521115802674, 39.95254397352744 ] } }, { "type" : "Feature", "properties" : { "OBJECTID" : 34556, "SPECIES" : null, "STATUS" : null, "DBH" : null }, "geometry" : { "type" : "Point", "coordinates" : [ -75.14507538603768, 39.952630343085694 ] } } ] }
                
        

        Now that your MongoDB database is set up, you can move on to creating your Flask app.

      Create your Flask App

      Now that your development environment is set up and your MongoDB data is stored in a database collection, you will create a simple GIS Flask application that will include a single Python file, app.py, a template file base.html, and some css styling stored in a map.css file. At the end of this section, you will be able to render a running Stadia Maps powered map in a browser window.

      Create your Project Layout

      1. Ensure you are in your stadia-maps project directory:

        cd ~/stadia-maps
        
      2. Create your Flask App’s project layout. The styles directory will store your app’s stylesheets, while the templates directory will store any Flask templates.

        mkdir -p static/styles && mkdir templates
        

      Create your app.py File

      In this section, you will write the code for your Flask application. Since this is a simple Flask app example, all your app code will be located in a file named app.py. If you are interested in viewing a more in-depth example of a Flask project layout, you can refer to Flask’s project layout documentation.

      1. In the root of your stadia-maps directory create a file named app.py and add the following import statements. This will ensure that your app has access to all the necessary Python packages and their methods.

        ~/stadia-maps/app.py
        1
        2
        3
        4
        5
        6
        7
        
        from flask import Flask, request, render_template
        from flask_session import Session
        from geojson import Point
        from flask_pymongo import PyMongo
        from bson.json_util import dumps
        import json
            
      2. Below your import statements, add the Python code to set up the Flask app and connect to your MongoDB database. The code creates an instance of the Flask class, connects to your MongoDB server and linodeStreetTrees database running on port 27017. Finally, an instance of the PyMongo class is created, which manages connections from MongoDB to your Flask app.

        ~/stadia-maps/app.py
         1
         2
         3
         4
         5
         6
         7
         8
         9
        10
        11
        
        from flask import Flask, request, render_template
        from flask_session import Session
        from geojson import Point
        from flask_pymongo import PyMongo
        from bson.json_util import dumps
        import json
        
        app=Flask(__name__)
        app.config["MONGO_URI"] = "mongodb://localhost:27017/linodeStreetTrees"
        mongo = PyMongo(app)
            
      3. Register your view function for your app’s index page by adding the example’s remaining lines to your app.py file. The route() decorator signals to Flask which URL should trigger the defined function def index(). When a user visits your app’s index (i.e. http://127.0.0.1:5000/), the code defined in the index() function will execute. This code retrieves all the data in your MongoDB linodeStreetTress collection and makes the data available to the base.html template in the street_trees_points template variable. The base.html template will be created in the next section.

        The final block of code provides a way for Python to handle both script execution and importing. Finally, if the conditional evaluates to true, it will execute Flask’s run() method to run your app.

        ~/stadia-maps/app.py
         1
         2
         3
         4
         5
         6
         7
         8
         9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        
        from flask import Flask, request, render_template
        from flask_session import Session
        from geojson import Point
        from flask_pymongo import PyMongo
        from bson.json_util import dumps
        import json
        
        app=Flask(__name__)
        app.config["MONGO_URI"] = "mongodb://localhost:27017/linodeStreetTrees"
        mongo = PyMongo(app)
        
        @app.route('/')
        def index():
            street_trees_points_query = dumps(mongo.db.linodeStreetTrees.find({}, {'_id': False}))
            street_trees_points = json.loads(street_trees_points_query)
            return render_template('base.html', street_trees_points=street_trees_points)
        
        if __name__ == '__main__':
            app.debug=True
            app.run()
        
            

      Create your Template File

      In this section you will create your Flask app’s template. Flask templates are used to render the front end portion of your app. You can make use of the Jinja templating language for Python to add additional functionality to your templates.

      In this example, the base.html template connects to Stadia Maps to retrieve their vector map tiles and renders them on your site’s index page. Stadia Map’s implementation relies on the Mapbox GL JavaScript library. The template accesses your database’s data and uses it to render marker’s on your map with the help of Mapbox GL and Stadia Maps. This example utilizes Stadia Map’s boiler plate vector maps example as a foundation. The example file is heavily commented, which you can use to better understand each section of the file.

      1. Create a file named base.html in your root project’s templates directory with the example file content.

        ~/stadia-maps/templates/base.html
         1
         2
         3
         4
         5
         6
         7
         8
         9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        33
        34
        35
        36
        37
        38
        39
        40
        41
        42
        43
        44
        45
        46
        47
        
        <html>
        <head>
            <title>Stadia Maps + Flask + MongoDB Demo</title>
            <meta charset="utf-8">
            <meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no" />
            <script src="//cdnjs.cloudflare.com/ajax/libs/mapbox-gl/0.52.0/mapbox-gl.js"></script>
            <link href="//cdnjs.cloudflare.com/ajax/libs/mapbox-gl/0.52.0/mapbox-gl.css" rel="stylesheet" />
            <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='../static/styles/map.css') }}" />
            <script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
        </head>
        <body>
            <h1>
                Stadia Maps + Flask + MongoDB Demo
            </h1>
            <div id="map"></div>
            <script>
             var map = new mapboxgl.Map({
               container: 'map',
               style: 'https://tiles.stadiamaps.com/styles/alidade_smooth.json',  // Theme URL; see our themes documentation for more options
               center: [-75.144867, 39.952278],  // Initial focus coordinate
               zoom: 16
             });
        
             mapboxgl.setRTLTextPlugin('https://api.mapbox.com/mapbox-gl-js/plugins/mapbox-gl-rtl-text/v0.2.1/mapbox-gl-rtl-text.js');
        
             // Add zoom and rotation controls to the map.
             map.addControl(new mapboxgl.NavigationControl());
        
             var mongoData = JSON.parse('{{ street_trees_points| tojson | safe }}');
             var markerCollection = mongoData[0];
        
                // Next, we can add markers to the map
                markerCollection.features.forEach(function(point) {
                    var elem = document.createElement('div');
                    elem.className = 'marker';
        
                    // Now, we construct a marker and set it's coordinates from the GeoJSON. Note the coordinate order.
                    var marker = new mapboxgl.Marker(elem);
                    marker.setLngLat(point.geometry.coordinates);
        
                    // Finally, we add the marker to the map.
                    marker.addTo(map);
                });
            </script>
        </body>
        </html>
              

        Key portions of the file that you should take note of are the following:

        • The <head> element contains a call to the Mapbox GL JavaScript file that the rest of the template makes use of. Other needed external scripts and files, like jQuery, are called in this section.
        • The call to the app’s stylesheet contains an href attribute (line 8) whose value makes use of the Jinja templating language. The code "{{ url_for('static', filename='../static/styles/map.css') }" allows Flask to generate URLs for any static files.
        • Line 15 (<div id="map"></div>) creates the div element that will contain the rendered map.
        • The script beginning on line 16 creates a new mapboxgl instance whose methods will be used throughout the script to construct your map. Notice that upon instantiation, Stadia Map’s theme URL is called to retrieve their Alidade Smooth theme. For details on all the MapBox GL methods that are used throughout the template, see the MapBox GL reference.
        • Line 29 is responsible for grabbing your databases data, using Jinja, from the street_trees_points variable that was exposed in the app.py file. The data is serialized and passed to the markerCollection variable. The forEach() method is then used to create markers for each collection document (from your database’s serialized data) and render them on your map.

      Create your App’s Styling

      In the previous section, the base.html template file calls your app’s style sheet. You are now ready to create the stylesheet.

      1. Create a file named map.css in your project’s ~/stadia-maps/static/styles directory and add the example file’s content. The marker that will be rendered on your map is provided by Stadia Map.

        ~/stadia-maps/static/styles/map.css
         1
         2
         3
         4
         5
         6
         7
         8
         9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        
        body {
          margin: 0;
          padding: 0;
        }
        
        #map {
          position: absolute;
          top: 0;
          bottom: 0;
          width: 100%;
        }
        
        /* We recommend using an icon that is 2x the intended display size, so that it renders nicely on retina displays.
        * The icon used in the background-image property is owned by Stadia Maps. While you maintain an account with us, we grant you royalty-free use of
        * this image when displayed on our maps.
        */
        .marker {
        background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADQAAABUCAYAAADNjBSxAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAACE1JREFUeNrUW0tsFEcQHQ/2wbZsIyUHJD62OAQJB+IzH2HlFD5KfMSAFKRE4nfxDeGTufA5ZS98DpFiJMN5EZ+zDZxyssG+Ii9WJA5B8lqyI4hgUm/Utaqp7Zmdnpn9pKSWd2G2p1+9rlfV3TNdQRB4Rdjg4OAI/Rk3DZ+PJVxeobZKbR5tY2Nj3ivIuvIAIhDb6c95077LMY4qtTK12bzgMgEyQErUJqgNecXaEvomYLMtAURgZujPVByQPXv2eAcPHvQOHDjgDQ8Ph9+lvXz50qtWq96bN2/Czwm2gPsQsMWmACIgY5gStqkFAGfPnvVOnTrl7d6928lBz549854+fRo2ALXYdQI1UyggAnPeTLEIK0ePHvWuXbvmHTlyJPa3kgUbY2w0aO/OnTve3bt3bcAwDcfpmvWGgwWgpDYwMDBDLZBtdHQ0oIEG2paWloIbN24Ex48fD/RvZDt06FBw4cKFYG5uLlhfX4/0QWCCq1ev2n63Sm2s0XgbgZnVHV+8eDG8KRsGRJ4N9u/fnwgiqQHcixcvIsBev34dOk5du94IVBKYKX3jhw8fRm4KD+/cuTMzEN1Onz4dVCqVCFsnTpywgRpxAkQ/mJCd7Nq1K6CgjbDSaFplbXAQHCUNs0Jdt0htu23sdaJgMv6iFID79+97Z86cCT/TVPDIa2EQN9NwP9xXfocSCntAYzivf+db+pqVYG7fvt1yMLBHjx55xIwnnYr0IOxncv5Eomwbef6DvyOvoONWg4ljam1tzSOFlLKOmnBMyrlvKWdCGxoaqnWEDtoBhpm6efNm+BlJe3p6Wv73sKla6hkiQPiP32xxMzk5GWZ0V8MUOXnyZJiA3717F7JMShlXETSsKNAPDIkcfYnCdqTGklC2VZk42Z48eeKsVFoVdeIkBzn3iWTMhqSu/n8qIttapmW+yZIw48BIs+SXhk3K+eHDhyNVBAPiGJqQscNTDdMDgehiKFIxzRqZlOS0RmVV7fPly5cjsWSK53pAUDY2AHI1+fskQ4BzTKQ1OJdzkeU+IQbfIBvSA0IQv3r1yhkQGE5rWDO5GgOicWtQ48zQuF4S6LK/WZZF7WS1oBxyjAGNSZkF8jyAXAaJVaurIRdi9kjny7LNNzs0ddOFf+RqWKCljQeRS5yMf4cFo7IRP25OZ70ZmOVyKcmQrLMaM2tZ7oeAjtkYylPmoKi8d+9eLDOQ9awOa2Aj3c0KeFpGh3sEUCJ2FKZxllTgYt3N7BwA0sZUUeab/S/bTo/3P7TViCjIRKoWUx1lLF6WOA8BrdpySNz+WScYj80iLOsRQPIC1zqrVYZQ4Nmjkz+2jX1zpBFZSHUyIFm/qUoj1AJfH18watCapXhsFSDEj9oFmpfLh8e24k+tOdpuqAwYkAIDK0tAZZk7eNphseZ6mtBMO3fuXFzNWOFjF9/QN2s2G+oulp20Wwx4xiC9KIUr2TYaSzKOOCehk05IsleuXKmVUKp8qprNUSugGku8F4ZO1F5YW9lBcasAlawbjeYfY1lqZyxJduRGibFZ60aj2D1d5T0G5CIWCKgK7wa1WtlWVlZqsYMdXGF1G/a+yrR1LDEgyGXS0WOzTG53cRiI2JnR19uOUyIsIcEuLy/XSqNWgsK9nj9/HseO9TDZt2xCrEvkyEu8pEYNdenSpbawI49WDDuluPWQbWcFF1fk6pNLdSheK2Qcp+tcVWM5rzZtSnEn4n5Cn1NyWYHlNMs4DsGaLQQs03CkUrZK0nMLfsL+V1muZhGQvM+NkqiZsQSHSZlWe31TjZbgSRbxhPTUrVu3miYEXIDCgapmWzCOzgbILC0e8HdkaE62zRIIKQSI3SQHZ2GorhOZC4oWCCkEcJzl1Hs+NyDqBDnpuq0kwjxHWVJ0vWZJoqnYSctQXeEqcwK8WkSdB7ZZCOAwtV9w3Ti2GEC6JJLJlgdTlEy7JNE8DDFLFZvi5V3ZSofAUWmTaC5ASSVRHpbgCDjE5ihXdlwZ4qV6oSwVyY4zIK02eVnS7FiW1iXXwWUBVJaKJweBwbnkJbkBY1E2Z3YyAUpaqvNyOa1JZVMlTiZ2sjJUl5c0S2kMy3nOO/L5A54FWdjJDMjcrCwBcSWO0iXNkySSHV6auFYFRTJUd9O5ubnULEEM5PmTEoOFtFVBoYDMTRdsg0L5nyQOMs6gkmq9U8rh5FwMRfbE9KM0SSzJIxEVO5VG651mAyq7igOOaHiJYDkSKeccTz5AWhzk4BAjtsohIZFGGG8XQxGvIhbko5y2R80SHl9bcn0TpSmAzJyv2ljSx5pyulme9ZktwLmFMOTFTTutdpIdy9Ne5Y4EhGkn1U6yJBOuEoOlPLmncEBaam3TTh7HWwDNF+TYwhiCPZaVs9xn00xZHv0sdyKgmpcR7Fzb8VOSEpBip1rka59FAop4WQY9wMhnHhRD8wWOoThAJqgrNkBQN8mQkuvOBKQHJx9bkdWBJX46GtCiZMH2mKfKP9UiqoOWMGQJ/tDUWy6LXtHW6DXKxDcSKb1Q+8q0vdT2DQwM/CXfUuHXQ22vc/b392MfbB+1HaKfnjxjSvVibldXFwb+tQHQZ/722K7t7e39sbu7+9cUvtza3Nz85cuXL5txOkPtX2ofTK24QWPdajjWOEAEAgP/xnivx4V18vyU7/vfJ4H5+PHj9KdPn966iim1tzTmtSyAfnAFIq2vr29y27ZtPxlG5RRfJiC/ZwAjbYX6eesKaNTERXbF8f3+np6evQTsW7rP5ufPn5dzAoH9Q+1P6m/DCZCYdjtMG8zDWAEg/qb2nsb7PlMMJQDsNQLhKZBf5RwwB/yW+L5F4/vg0tF/AgwACuIdiHmTJCAAAAAASUVORK5CYII=');
        background-size: cover;
        width: 27px;
        height: 42px;
        cursor: pointer;
        }
            

      Run your Flask App

      You are now ready to run your Flask app locally to view your rendered Stadia Map.

      1. Open a new terminal window and navigate to your stadia-maps root directory.

        cd ~/stadia-maps
        
      2. Run your Flask application with the following command:

        python3 app.py
        

        You will see that your Flask server runs with the following output:

          
        * Serving Flask app "app" (lazy loading)
        * Environment: production
          WARNING: This is a development server. Do not use it in a production deployment.
          Use a production WSGI server instead.
        * Debug mode: on
        * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
        * Restarting with stat
        * Debugger is active!
        * Debugger PIN: 140-560-688
            
        
      3. Open a browser and navigate to the local URL. In our example, this is: http://127.0.0.1:5000/. You should see a Stadia Map render that displays the location of Linode’s headquarters and the location of some of the neighborhood’s surrounding trees.

        "Linode Trees"

      Next Steps

      This guide is published under a CC BY-ND 4.0 license.





      Source link

      Understanding Maps in Go


      Most modern programming languages have the concept of a dictionary or a hash type. These types are commonly used to store data in pairs with a key that maps to a value.

      In Go, the map data type is what most programmers would think of as the dictionary type. It maps keys to values, making key-value pairs that are a useful way to store data in Go. A map is constructed by using the keyword map followed by the key data type in square brackets [ ], followed by the value data type. The key-value pairs are then placed inside curly braces on either side { }:

      map[key]value{}
      

      You typically use maps in Go to hold related data, such as the information contained in an ID. A map with data looks like this:

      map[string]string{"name": "Sammy", "animal": "shark", "color": "blue", "location": "ocean"}
      

      In addition to the curly braces, there are also colons throughout the map that connect the key-value pairs. The words to the left of the colons are the keys. Keys can be any comparable type in Go, like strings, ints, and so on.

      The keys in the example map are:

      • "name"
      • "animal"
      • "color"
      • "location"

      The words to the right of the colons are the values. Values can be any data type. The values in the example map are:

      • "Sammy"
      • "shark"
      • "blue"
      • "ocean"

      Like the other data types, you can store the map inside a variable, and print it out:

      sammy := map[string]string{"name": "Sammy", "animal": "shark", "color": "blue", "location": "ocean"}
      fmt.Println(sammy)
      

      This would be your output:

      Output

      map[animal:shark color:blue location:ocean name:Sammy]

      The order of the key-value pairs may have shifted. In Go, the map data type is unordered. Regardless of the order, the key-value pairs will remain intact, enabling you to access data based on their relational meaning.

      Accessing Map Items

      You can call the values of a map by referencing the related keys. Since maps offer key-value pairs for storing data, they can be important and useful items in your Go program.

      If you want to isolate Sammy’s username, you can do so by calling sammy["name"]; the variable holding your map and the related key. Let’s print that out:

      fmt.Println(sammy["name"])
      

      And receive the value as output:

      Output

      Sammy

      Maps behave like a database; instead of calling an integer to get a particular index value as you would with a slice, you assign a value to a key and call that key to get its related value.

      By invoking the key "name" you receive the value of that key, which is "Sammy".

      Similarly you can call the remaining values in the sammy map using the same format:

      fmt.Println(sammy["animal"])
      // returns shark
      
      fmt.Println(sammy["color"])
      // returns blue
      
      fmt.Println(sammy["location"])
      // returns ocean
      

      By making use of the key-value pairs in map data types, you can reference keys to retrieve values.

      Keys and Values

      Unlike some programming languages, Go does not have any convenience functions to list out the keys or values of a map. An example of this would be Python’s .keys() method for dictionaries. It does, however, allow for iteration by using the range operator:

      for key, value := range sammy {
          fmt.Printf("%q is the key for the value %qn", key, value)
      }
      

      When ranging through a map in Go, it’ll return two values. The first value will be the key, and the second value will be the value. Go will create these variables with the correct data type. In this case, the map key was a string so key will also be a string. The value is also a string:

      Output

      "animal" is the key for the value "shark" "color" is the key for the value "blue" "location" is the key for the value "ocean" "name" is the key for the value "Sammy"

      To get a list of just the keys, you can use the range operator again. You can declare just one variable to only access the keys:

      keys := []string{}
      
      for key := range sammy {
          keys = append(keys, key)
      }
      fmt.Printf("%q", keys)
      

      The program begins by declaring a slice to store your keys in.

      The output will show only the keys of your map:

      Output

      ["color" "location" "name" "animal"]

      Again, the keys are not sorted. If you want to sort them, you use the sort.Strings function from the sort package:

      sort.Strings(keys)
      

      With this function, you’ll receive the following output:

      Output

      ["animal" "color" "location" "name"]

      You can use the same pattern to retrieve just the values in a map. In the next example, you pre-allocate the slice to avoid allocations, thus making the program more efficient:

      sammy := map[string]string{"name": "Sammy", "animal": "shark", "color": "blue", "location": "ocean"}
      
      items := make([]string, len(sammy))
      
      var i int
      
      for _, v := range sammy {
          items[i] = v
          i++
      }
      fmt.Printf("%q", items)
      

      First you declare a slice to store your keys in; since you know how many items you need, you can avoid potential memory allocations by defining the slice at the exact same size. You then declare your index variable. As you don’t want the key, you use the _ operator, when starting your loop, to ignore the key’s value. Your output would be the following:

      Output

      ["ocean" "Sammy" "shark" "blue"]

      To determine the number of items in a map, you can use the built-in len function:

      sammy := map[string]string{"name": "Sammy", "animal": "shark", "color": "blue", "location": "ocean"}
      fmt.Println(len(sammy))
      

      The output displays the number of items in your map:

      Output

      4

      Even though Go doesn’t ship with convenience functions to get keys and values, it only takes a few lines of code to retrieve the keys and values when needed.

      Checking Existence

      Maps in Go will return the zero value for the value type of the map when the requested key is missing. Because of this, you need an alternative way to differentiate a stored zero, versus a missing key.

      Let’s look up a value in a map that you know doesn’t exist and look at the value returned:

      counts := map[string]int{}
      fmt.Println(counts["sammy"])
      

      You’ll see the following output:

      Output

      0

      Even though the key sammy was not in the map, Go still returned the value of 0. This is because the value data type is an int, and because Go has a zero value for all variables, it returns the zero value of 0.

      In many cases, this is undesirable and would lead to a bug in your program. When looking up the value in a map, Go can return a second, optional value. This second value is a bool and will be true if the key was found, or false if the key was not found. In Go, this is referred to as the ok idiom. Even though you could name the variable that captures the second argument anything you want, in Go, you always name it ok:

      count, ok := counts["sammy"]
      

      If the key sammy exists in the counts map, then ok will be true. Otherwise ok will be false.

      You can use the ok variable to decide what to do in your program:

      if ok {
          fmt.Printf("Sammy has a count of %dn", count)
      } else {
          fmt.Println("Sammy was not found")
      }
      

      This would result in the following output:

      Output

      Sammy was not found

      In Go, you can combine variable declaration and conditional checking with an if/else block. This allows you to use a single statement for this check:

      if count, ok := counts["sammy"]; ok {
          fmt.Printf("Sammy has a count of %dn", count)
      } else {
          fmt.Println("Sammy was not found")
      }
      

      When retrieving a value from a map in Go, it’s always good practice to check for its existence as well to avoid bugs in your program.

      Modifying Maps

      Maps are a mutable data structure, so you can modify them. Let’s look at adding and deleting map items in this section.

      Adding and Changing Map Items

      Without using a method or function, you can add key-value pairs to maps. You do this using the maps variable name, followed by the key value in square brackets [ ], and using the equal = operator to set a new value:

      map[key] = value
      

      In practice, you can see this work by adding a key-value pair to a map called usernames:

      usernames := map[string]string{"Sammy": "sammy-shark", "Jamie": "mantisshrimp54"}
      
      usernames["Drew"] = "squidly"
      fmt.Println(usernames)
      

      The output will display the new Drew:squidly key-value pair in the map:

      Output

      map[Drew:squidly Jamie:mantisshrimp54 Sammy:sammy-shark]

      Because maps are returned unordered, this pair may occur anywhere in the map output. If you use the usernames map later in your program file, it will include the additional key-value pair.

      You can also use this syntax for modifying the value assigned to a key. In this case, you reference an existing key and pass a different value to it.

      Consider a map called followers that tracks followers of users on a given network. The user "drew" had a bump in followers today, so you need to update the integer value passed to the "drew" key. You’ll use the Println() function to check that the map was modified:

      followers := map[string]int{"drew": 305, "mary": 428, "cindy": 918}
      followers["drew"] = 342
      fmt.Println(followers)
      

      Your output will show the updated value for drew:

      Output

      map[cindy:918 drew:342 mary:428]

      You see that the number of followers jumped from the integer value of 305 to 342.

      You can use this method for adding key-value pairs to maps with user input. Let’s write a quick program called usernames.go that runs on the command line and allows input from the user to add more names and associated usernames:

      usernames.go

      package main
      
      import (
          "fmt"
          "strings"
      )
      
      func main() {
          usernames := map[string]string{"Sammy": "sammy-shark", "Jamie": "mantisshrimp54"}
      
          for {
              fmt.Println("Enter a name:")
      
              var name string
              _, err := fmt.Scanln(&name)
      
              if err != nil {
                  panic(err)
              }
      
              name = strings.TrimSpace(name)
      
              if u, ok := usernames[name]; ok {
                  fmt.Printf("%q is the username of %qn", u, name)
                  continue
              }
      
              fmt.Printf("I don't have %v's username, what is it?n", name)
      
              var username string
              _, err = fmt.Scanln(&username)
      
              if err != nil {
                  panic(err)
              }
      
              username = strings.TrimSpace(username)
      
              usernames[name] = username
      
              fmt.Println("Data updated.")
          }
      }
      

      In usernames.go you first define the original map. You then set up a loop to iterate over the names. You request your user to enter a name and declare a variable to store it in. Next, you check to see if you had an error; if so, the program will exit with a panic. Because Scanln captures the entire input, including the carriage return, you need to remove any space from the input; you do this with the strings.TrimSpace function.

      The if block checks whether the name is present in the map and prints feedback. If the name is present it then continues back to the top of the loop. If the name is not in the map, it provides feedback to the user and then will ask for a new username for the associated name. The program checks again to see if there is an error. With no error, it trims off the carriage return, assigns the username value to the name key, and then prints feedback that the data was updated.

      Let’s run the program on the command line:

      You'll see the following output:

      Output

      Enter a name: Sammy "sammy-shark" is the username of "Sammy" Enter a name: Jesse I don't have Jesse's username, what is it? JOctopus Data updated. Enter a name:

      When you're done testing, press CTRL + C to escape the program.

      This shows how you can modify maps interactively. With this particular program, as soon as you exit the program with CTRL + C you’ll lose all your data unless you implement a way to handle reading and writing files.

      To summarize, you can add items to maps or modify values with the map[key] = value syntax.

      Deleting Map Items

      Just as you can add key-value pairs and change values within the map data type, you can also delete items within a map.

      To remove a key-value pair from a map, you can use the built-in function delete(). The first argument is the map you are deleting from. The second argument is the key you are deleting:

      delete(map, key)
      

      Let's define a map of permissions:

      permissions := map[int]string{1: "read", 2: "write", 4: "delete", 8: "create", 16:"modify"}
      

      You no longer need the modify permission, so you'll remove it from your map. Then you'll print out the map to confirm it was removed:

      permissions := map[int]string{1: "read", 2: "write", 4: "delete", 8: "create", 16: "modify"}
      delete(permissions, 16)
      fmt.Println(permissions)
      

      The output will confirm the deletion:

      Output

      map[1:read 2:write 4:delete 8:create]

      The line delete(permissions, 16) removes the key-value pair 16:"modify" from the permissions map.

      If you would like to clear a map of all of its values, you can do so by setting it equal to an empty map of the same type. This will create a new empty map to use, and the old map will be cleared from memory by the garbage collector.

      Let’s remove all the items within the permissions map:

      permissions = map[int]string{}
      fmt.Println(permissions)
      

      The output shows that you now have an empty map devoid of key-value pairs:

      Output

      map[]

      Because maps are mutable data types, they can be added to, modified, and have items removed and cleared.

      Conclusion

      This tutorial explored the map data structure in Go. Maps are made up of key-value pairs and provide a way to store data without relying on indexing. This allows us to retrieve values based on their meaning and relation to other data types.



      Source link