如何使用GeoJson和Javascript建立一个地点定位器

83 阅读9分钟

使用GeoJson和Javascript构建一个地点定位器

GeoJson是一种使用JavaScript Object Notation格式在数据库中存储地理点的传统方法。由于基于JSON,GeoJson很容易与JavaScript集成。此外,GeoJson允许对地理特征进行编码,如纬度、经度、线型字符串和多边形。

本文将引导读者使用GeoJson来构建一个使用Javascript的地点定位器应用程序。在文章中,我们将开发一个应用程序,将一个地点添加到MongoDB数据库中,对其进行地理编码以获得该地点的纬度、经度和可读地址,然后在地图上显示该地点的经纬度图。

这个项目探索了几个与GeoJson一起工作的API。使用的API是Mapbox,用于在网页上渲染地图,mapquest用于发送位置请求,并将地点的名称地理编码为匹配的经纬度。

前提条件

要跟上这篇文章,你需要具备以下条件。

  • 一个代码编辑器。我更喜欢[VS Code],因为它提供了对网络开发有帮助的扩展。
  • Mapquest帐户的API密钥。
  • 拥有mapquest API密钥的[Mapbox账户]。
  • 对Javascript的理解。
  • Node.js的工作安装。

项目环境设置

运行npm init -y,创建一个空的package.json 文件来开始。该文件包含开发和运行应用程序所需的所有依赖项。在这个文件中包含依赖项是非常重要的,这样当项目在本地开发环境之外的任何地方执行时,用户只需执行一条命令就可以让项目运行。

接下来,执行下面的命令来安装所需的包。

npm install express cors node-geocoder mongoose dotenv

在成功安装了依赖项之后,我们需要为项目设置一个mongo DB数据库。人们可以按照这些步骤来设置数据库,并获得一个他将用于项目的连接URL

在下一步,登录或创建一个Mapbox的新账户,以获得一个API密钥,在项目网站上生成地图。然后,应与Mapquest进行同样的程序。

然而,密钥需要在mapquest中是全局性的,而Mapbox的API密钥则带有一个启动代码,我们将在网站文件夹中的JavaScript文件中使用。

Mapbox和Mapquest都是用来绘制位置数据,并分别对地名的纬度和经度进行地理编码。Mapquest确保给定一个地名,就会返回与该地相关的所有信息,包括格式化的地址、纬度、经度、街道名称、邮政编码和国家。然而,我们的项目只需要经纬度,以便在Mapbox提供的地图上定位该地。

文件夹组织

我们将使用MVC开发模式。然而,项目的规模将与之有一些偏差。因此,我们将把我们的模型、控制器和路由分离到不同的文件夹中。此外,我们将有一个网站文件夹,包含在用户界面上渲染数据所需的文件。最后,我们将有一个实用文件夹,包含项目所需的实用文件。

项目的最终文件夹组织如下所示。

developer-locator
    ┣ config
    ┣ controllers
    ┣ models
    ┣ routes
    ┣ utilities
    ┣ website
    ┗ package.json

上面的每个文件夹都会有我们稍后要讨论的文件。

设置项目服务器

在设置服务器文件之前,我们需要将全局变量存储在一个配置文件中。因此,创建一个名为config.env 的新文件,并在配置文件夹中添加以下代码段。

PORT=5000
MODE=development
MONGO_URI='YOUR MONGO URI'
PROVIDER=mapquest
GEOCODER_API_KEY='YOUR GEOCODER API KEY'

在应用程序的根文件夹中创建一个名为server.js 的新文件。该文件将包含一个项目的所有服务器配置,包括运行的端口、所需的依赖性和全局变量。在server.js 文件中添加以下片段。

const path = require('path');
const express = require('express')
const dotenv = require('dotenv')
const cors = require('cors')

///load environmental variables into the project
dotenv.config({ path: './config/config.env' });

//initialize the application
const app = express()

//uloading the bosy parser middleware
app.use(express.json())

//using cors
app.use(cors())

const PORT = process.env.PORT || 5000;

app.listen(PORT, () =>{
    console.log(`Server running in ${process.env.MODE} mode on port ${PORT}`);
})

设置数据库连接

我们需要数据库来存储开发者的信息,包括姓名、地点和在系统中注册的日期。首先,我们需要设置一个数据库连接到我们之前创建的远程mongo DB数据库。在config 文件夹中,创建一个名为database.js 的新文件,并在下面的片段。

const mongoose = require('mongoose');

const connectDatabase = async() => {
    try {
        const connection = await mongoose.connect(YOUR MONGO URI, {
            useUnifiedTopology: true,
        });

        console.log(`Database connected to ${connection.connection.host}`)
    } catch (error) {
        console.log(error);
        process.exit(1);
    }
}
module.exports = connectDatabase;

下一步是在服务器文件中加载数据库连接函数。然后,在server.js 文件中添加以下代码段,以方便数据库连接。

const connectDatabase = require('./config/database')

connectDatabase();

创建开发者模型

一个模型就像一个脚手架,它提供了一种在数据库中表示数据的方式。它指定了一个给定数据元素的所有属性和每个属性的数据类型。

我们将为我们的案例存储开发者的名字、地址、地点和他的注册日期。在models 文件夹中创建一个名为Developer.js 的文件。下面这段代码代表了一个开发商的模式。

const mongoose = require('mongoose');
const DeveloperSchema = new mongoose.Schema(
    {
        name:{
            type: String,
            required: [true, 'Please add developer name']
        },
        address: {
            type: String,
            required: [true, 'Please add developer address']
        },
        location: {
            type: {
                type: String,
                enum: ['Point']
            },
            coordinates: {
                type: [Number],
                index: '2dsphere'
            },
            readableAddress: String
        },
        dateRegistered: {
            type: Date,
            default: Date.now
        }
    }
);

在创建了开发者模式之后,我们需要添加一个中间件,该中间件可以获取用户提供的位置字符串,并将其地理编码为纬度、经度和可读地址,然后将其存储在位置对象的相应属性中。

所有这些都是在开发者对象被保存到数据库之前完成的。我们还将this.location ,以防止地理编码的位置数据被保存到数据库中。

//geocode and create location
DeveloperSchema.pre('save', async function(next){
    const locate = await geocoder.geocode(this.address);
    this.location = {
        type:  'Point',
        coordinates: [locate[0].latitude, locate[0].longitude],
        readableAddress: locate[0].formattedAddress
    }


    this.address = undefined;
    next();
});

为了使上述中间件工作,我们需要添加地理编码工具,包括服务提供者和API密钥。这个工具从我们下载的node-geocoder 依赖关系中获得。

utilities 文件夹中,创建一个名为geocoder.js 的新文件,然后添加以下代码片段。

const nodeGeocoder = require('node-geocoder');
const options = {
    provider: process.env.PROVIDER,
    httpAdapter: 'https',
    apiKey: process.env.GEOCODER_API_KEY,
    formatter: null
};

const geocoder = nodeGeocoder(options);
module.exports = geocoder;

在控制器上工作

控制器决定了如何获得数据并将其输入数据库。我们将有两个方法,分别完成这些功能。首先,在controllers 文件夹中,创建一个名为developer.js 的新文件,然后添加下面的代码片断。

const Developer = require('../models/Developer')

exports.getDevelopers = async (req, res, next) =>{
    try {
        const developers = await Developer.find();
        return res.status(200).json({
            data: developers
        })
    } catch (error) {
        res.status(500).json({
            error: error,
        })
    }
}


exports.addDeveloper = async (req, res, next) =>{
    try {
        const developer = await Developer.create(req.body);
        return res.status(200).json({
            data: developer
        })
    } catch (error) {
        res.status(500).json({
            error: error.message,
        })
    }
}

将数据传递给路由器

路由是必要的,它可以指定在哪里引导请求,以及URL做获取数据的地方。我们的路由文件从控制器中获取功能,并根据请求应用必要的功能。

我们将实例化一个路由器,并从控制器中传递功能,然后将文件导出到服务器文件中,这是应用程序的入口点。

const express = require('express')
const {getDevelopers, addDeveloper} = require('../controllers/developers')
const router = express.Router()
router.route('/').get(getDevelopers).post(addDeveloper);
module.exports = router

在网站上工作

我们的网站将有一个标题和一个地图区域,显示在一个给定的地方有哪些开发商。

此外,我们将有一个表格,用于将开发人员添加到系统中。该表格将提交数据以调用controller 文件中的addDeveloper 函数。

接下来,我们有一个JavaScript文件,在浏览器上渲染地图。一旦你在Mapbox创建了API密钥,就会提供这个代码片段。

mapboxgl.accessToken = 'yourmapboxapikey;
const map = new mapboxgl.Map({
    container: 'map',
    style: 'mapbox://styles/mapbox/streets-v11', //,ap style
    zoom: 9, //zoom level
    center: [ 36.82194619999996, -1.2920659] //the center of the map
})

该代码段指定了缩放级别、地图风格和中心,中心的选择是基于你想在哪里映射出开发者。

获取开发人员并将其显示在地图上

在这一步,我们要收集数据库中的所有开发人员,然后根据具体的经纬度在地图上显示他们。

我们首先需要一个函数来从数据库中获取所有的开发者。接下来,我们将把开发人员转换为一个数组,并对其进行修改以适应Mapbox指定的风格。

async function getDevelopers() {
    const res = await fetch('/api/developers');
    const data = await res.json();


    const developers = data.data.map(developer => {
        return {
            type: 'Feature',
            geometry: {
                type: 'Point',
                coordinates: [ 
                    developer.location.coordinates[0], //latitude
                    developer.location.coordinates[1] // longitude
                ]
            },
            properties: {
                name: developer.name, //developer name
                icon: 'house' //icon to display
            }
        };
    });
    
    loadMap(developers); //load map with developers
}

下面的函数加载地图,将开发者映射到各自的经纬度上。接下来,我们将上述函数中的开发者数组传递给loadMap() 函数。我们还设置了图标的大小,并添加了开发者的名字。

最后,我们调用getDevelopers 函数,该函数调用loadMap 函数。

//Load map with devs
function loadMap(developers) {
    map.on('load', function() {
        map.addLayer({
            id: 'points',
            type: 'symbol',
            source: {
                type: 'geojson',
                data: {
                    type: 'FeatureCollection',
                    features: developers //add the develpers array
                }
            },
            layout: {
                'icon-image': '{icon}-15',
                'icon-size': 1.5,
                'text-field': '{name}',
                'text-anchor': 'top'
            }
        });
    });
}

getDevelopers()

测试应用程序

要运行该应用程序,使用npm start 来启动你的服务器,然后前往你在config.env 文件中指定的端口上的localhost来访问你的网站。

在添加开发人员之前,你的网站应该如下图所示。Map without developers

然而,在添加了开发人员之后,地图应该如下所示。

Map with developers

结论

本文介绍了如何使用Mapbox与GeoJson来构建一个在地图上定位开发人员的应用程序。我们探讨了如何将位置名称地理编码为经纬度,然后存储在MongoDB数据库中。

然后用Mapbox将存储的开发者的位置钉在地图上。在这个项目中探索GeoJson、Mapbox和Mapquest可以成为使用地图和地理编码的一个很好的起点。