gatsby_如何使用Gatsby和Leaflet创建夏季公路旅行地图绘制应用程序

gatsby

Get ready for the summer by building your own road trip mapping app with this step-by-step guide!

通过此逐步指南,构建自己的公路旅行地图应用,为夏天做好准备!

Author’s Note: Even though we’re going through some challenging times, we can still be optimistic that we’ll get through this together and be able to enjoy our summer. Stay safe and wash your hands. ❤️

作者的注释:即使我们正在经历一些艰难的时期,我们仍然很乐观,因为我们会一起度过难关并能够享受我们的夏天。 保持安全并洗手。 ❤️

我们要建造什么? (What are we going to build?)

We’ll be walking through building a new mapping app that shows a route representing the trip. Each location will have a little card where we can add a picture and some things we did.

我们将逐步构建一个新的地图应用程序,该应用程序显示代表行程的路线。 每个位置都会有一张小卡片,我们可以在其中添加图片和所做的一些事情。

To get started, we’re going to use this Leaflet Gatsby Starter I created to make the initial setup a little smoother. With our app bootstrapped, we’ll create our list of locations and use Leaflet’s API to draw our route on the map.

首先,我们将使用我创建的Leaflet Gatsby Starter简化初始设置。 引导我们的应用程序后,我们将创建位置列表并使用Leaflet的API在地图上绘制路线。

哇,地图应用? (Woah, a mapping app?)

Yup. If you haven’t played with maps before, don’t be discouraged! It's not as bad as you probably think. If you’d rather start with mapping basics, you can read more about how mapping works first.

对。 如果您以前从未玩过地图,请不要气!! 它并不像您想象的那么糟糕。 如果您想开始使用映射基础知识,则可以阅读有关映射如何首先工作的更多信息

开始之前需要什么? (What do we need before we get started?)

If you followed along with my last tutorial for building a Santa Tracker, you can follow the same steps to get started. If not, we’ll want to make sure we have the following set up:

如果您遵循了我的上一本有关构建Santa Tracker的教程,则可以按照相同的步骤开始。 如果没有,我们将确保我们进行以下设置:

If you’re not sure about one of the above items, you can try checking out the beginning of my previous tutorial.

如果您不确定上述项目之一,可以尝试查看上一教程的开头。

We’ll also want to set up a foundation for our map. We can do this by utilizing the Leaflet Gatsby Starter I put together that provides us a basic setup with Leaflet and React Leaflet.

我们还想为我们的地图建立基础。 我们可以利用我组合在一起的Leaflet Gatsby Starter来做到这一点,它为我们提供了LeafletReact Leaflet的基本设置。

gatsby new my-road-trip https://github.com/colbyfayock/gatsby-starter-leaflet

After that’s finished running, you can navigate to the newly created project directory and start your local development server:

完成运行后,您可以导航到新创建的项目目录并启动本地开发服务器:

cd my-road-trip
yarn develop

If all goes as planned, your server should start and you should now be able to see your basic mapping app in your browser!

如果一切按计划进行,则服务器将启动,您现在应该可以在浏览器中看到基本的地图应用程序!

步骤1:清理一些不需要的代码 (Step 1: Cleaning up some unneeded code)

The Gatsby Starter we're using to spin up this app comes with some demo code that we don’t need here. We’ll want to make all of the changes below in the file src/pages/index.js, which is the homepage of our app.

我们用来启动该应用程序的Gatsby Starter附带了一些此处不需要的演示代码。 我们要在文件src/pages/index.js以下所有更改,这是我们应用程序的主页。

First, let’s remove everything from the mapEffect function. This function is used to run code that fires when the map renders.

首先,让我们从mapEffect函数中删除所有内容。 此函数用于运行在地图渲染时触发的代码。

// In src/pages/index.js

async function mapEffect({ leafletElement } = {}) {
  // Get rid of everything in here
}

Next, we don’t want a marker this time, so let’s remove the <Marker component from our <Map component:

接下来,我们这次不想要标记,因此让我们从<Map组件中删除<Marker组件:

<Map {…mapSettings} />

Now that we have those pieces cleared out, we can remove all of the following imports and variables from the top of our file:

现在,我们已经清除了这些部分,可以从文件顶部删除以下所有导入和变量:

  • useRef

    useRef
  • Marker

    记号笔
  • promiseToFlyTo

    promiseToFlyTo
  • getCurrentLocation

    getCurrentLocation
  • gatsby_astronaut

    gatsby_astronaut
  • timeToZoom

    timeToZoom
  • timeToOpenPopupAfterZoom

    timeToOpenPopupAfterZoom
  • timeToUpdatePopupAfterZoom

    timeToUpdatePopupAfterZoom
  • ZOOM

    放大
  • popupContentHello

    popupContentHello
  • popupContentGatsby

    popupContentGatsby
  • markerRef

    markerRef

After, our map should still work, but not do anything.

之后,我们的地图应该仍然有效,但是什么也没做。

Follow along with the commit

跟着提交

步骤2:建立公路旅行地点 (Step 2: Create our road trip locations)

This step will involve preparing our location data that will populate our road trip app. Our locations will include properties like a name, date, things we did, and a picture if we want.

此步骤将涉及准备将填充我们的公路旅行应用程序的位置数据。 我们的位置将包括属性,例如名称,日期,我们做的事情,以及如果需要的图片。

First, create a new file in the src/data directory called locations.js.  Inside of that file, we want to create and export a new array.

首先,在src/data目录中创建一个名为locations.js的新文件。 在该文件内部,我们要创建和导出一个新数组。

export const locations = [
  {
    placename: ‘Herndon, VA’,
    date: ‘August 1, 2015’,
    location: {
      lat: 38.958988,
      lng: -77.417320
    },
    todo: [
      ‘Where we start! 🚀’
    ]
  },
  {
    placename: ‘Middlesboro, KY',
    date: ‘August 1, 2015’,
    location: {
      lat: 36.627517,
      lng: -83.621635
    },
    todo: [
      ‘Cumberland Gap 🌳’
    ]
  }
];

You can use the above to get started, but you’ll eventually want to change the details to something of your choosing.

您可以使用以上内容开始使用,但最终您需要将详细信息更改为您选择的内容。

If you want to add an image to your location, you can do so by including an image property to the object. You can use either a URL string or you can import a local file if you have one available, like I’m doing in this example:

如果要将图像添加到您的位置,可以通过在对象中包含image属性来实现。 您可以使用URL字符串,也可以导入本地文件(如果有可用的话),例如我在本示例中所做的:

import imgHerndonStart from 'assets/images/herndon-start.jpg’;

export const locations = [
  {
    placename: ‘Herndon, VA’,
    date: ‘August 1, 2015’,
    image: imgHerndonStart,
    location: {
      lat: 38.958988,
      lng: -77.417320
    },
    todo: [
      ‘Where we start! 🚀’
    ]
  }
]

Once we have that file created, we can now import our locations into our src/pages/index.js file so we can use it in our next step:

创建完该文件后,我们现在可以将位置导入到src/pages/index.js文件中,以便在下一步中使用它:

import { locations } from 'data/locations’;

If you add a console.log(locations) inside of your page, you should now see all of your location data in an array!

如果在页面内添加console.log(locations) ,现在应该在数组中看到所有位置数据!

Follow along with the commit

跟着提交

步骤3:为我们的应用准备一些功能 (Step 3: Prepare our app with some functions)

To try to keep things simple and focused, I grouped together 3 important components of creating our map into functions. Though it’s available to copy and paste, we’ll walk through what’s happening in each function.

为了使事情简单而集中,我将创建地图的3个重要组件归为一组。 尽管可以复制和粘贴,但我们将逐步介绍每个函数中发生的情况。

You can place each of these functions at the bottom of the src/pages/index.js file so they’re ready to use in our next step.

您可以将每个函数放在src/pages/index.js文件的底部,以便在下一步中使用它们。

createTripPointsGeoJson (createTripPointsGeoJson)

Our first function is going to take the array of our locations and return a GeoJSON document, with our locations mapped into an individual Feature. We’ll use this function to create the individual points on our map.

我们的第一个函数将获取我们的位置数组,并返回一个GeoJSON文档 ,并将我们的位置映射到一个单独的Feature中。 我们将使用此功能在地图上创建各个点。

What is a GeoJSON document? It's essentially a JavaScript object or JSON document with a specific structure that creates consistency with geographical data.

什么是GeoJSON文档? 本质上,它是具有特定结构JavaScript对象或JSON文档,可与地理数据保持一致。

function createTripPointsGeoJson({ locations } = {}) {
  return {
    “type”: “FeatureCollection”,
    “features”: locations.map(({ placename, location = {}, image, date, todo = [] } = {}) => {
      const { lat, lng } = location;
      return {
        “type”: “Feature”,
        “properties”: {
          placename,
          todo,
          date,
          image
        },
        “geometry”: {
          “type”: “Point”,
          “coordinates”: [ lng, lat ]
        }
      }
    })
  }
}

So what’s happening in the above?

那么上面发生了什么?

  • We take an argument of locations, which will be our array of destinations

    我们接受一个关于位置的参数,这将是我们的目的地数组
  • We return an object with some dynamic properties associated with it

    我们返回一个具有一些动态属性的对象
  • Within the object, we map our locations to individual Feature objects

    在对象内,我们将位置映射到各个Feature对象

  • Each object includes a Point shape using our coordinates

    使用我们的坐标,每个对象都包含一个Point形状

  • It additionally includes our properties that store our metadata

    它还包括存储我们的元数据的属性

When this function is invoked, we will have a newly created JavaScript object that includes an array of Points representing the locations we are stopping at on our road trip.

调用此函数后,我们将创建一个新创建JavaScript对象,该对象包括一个Points数组,这些Points表示我们在旅途中要停靠的位置。

createTripLinesGeoJson (createTripLinesGeoJson)

We’re going to create another function that’s similar to the previous one. This time however, instead of points, we want to create lines that represent going from one point to the next.

我们将创建另一个与上一个功能相似的功能。 但是,这次,我们要创建代表从一个点到下一个点的线,而不是点。

function createTripLinesGeoJson({ locations } = {}) {
  return {
    “type”: “FeatureCollection”,
    “features”: locations.map((stop = {}, index) => {
      const prevStop = locations[index - 1];

      if ( !prevStop ) return [];

      const { placename, location = {}, date, todo = [] } = stop;
      const { lat, lng } = location;
      const properties = {
        placename,
        todo,
        date
      };

      const { location: prevLocation = {} } = prevStop;
      const { lat: prevLat, lng: prevLng } = prevLocation;

      return {
        type: ‘Feature’,
        properties,
        geometry: {
          type: ‘LineString’,
          coordinates: [
            [ prevLng, prevLat ],
            [ lng, lat ]
          ]
        }
      }
    })
  }
}

So you’ll immediately notice that this is very similar to our last function. We’re returning an object and setting our metadata properties on a list of Features.

因此,您会立即注意到,这与我们的上一个功能非常相似。 我们将返回一个对象,并在功能列表上设置元数据属性。

The big difference, however, is that we're creating a Line. To do this, we're looking up and referring to prevStop which will be the previous stop. We’ll use both the previous stop and our current stop in order to have 2 points which we can use to draw the line.

但是,最大的不同是我们正在创建一条线。 为此,我们要查找并引用prevStop ,这将是上一站。 我们将使用上一个停靠点和当前停靠点,以便有2个点可以用来画线。

If we don’t have a previous stop, we return an empty array, which basically means we’re at the beginning of our journey with no line before it.

如果我们没有上一个停靠点,那么我们将返回一个空数组,这基本上意味着我们正处在旅途的开始,之前没有任何一行。

With the previous stop and current stop, we create a LineString type of Feature with our 2 points.

使用上一个停靠点和当前停靠点,我们用2个点创建LineString类型的Feature。

tripStopPointToLayer (tripStopPointToLayer)

Our last function is going to allow us to create custom content for each of the points that we will be adding to our map. We’ll actually be utilizing this function within a Leaflet property, so we’ll be conforming our arguments to that specification.

我们的最后一个功能是允许我们为要添加到地图中的每个点创建自定义内容。 实际上,我们将在Leaflet属性中利用此函数,因此我们将使参数符合该规范。

function tripStopPointToLayer( feature = {}, latlng ) {
  const { properties = {} } = feature;
  const { placename, todo = [], image, date } = properties;

  const list = todo.map(what => `<li>${ what }</li>`);
  let listString = ‘’;
  let imageString = ‘’;

  if ( Array.isArray(list) && list.length > 0 ) {
    listString = list.join(‘’);
    listString = `
      <p>Things we will or have done…</p>
      <ul>${listString}</ul>
    `
  }

  if ( image ) {
    imageString = `
      <span class=“trip-stop-image” style=“background-image: url(${image})”>${placename}</span>
    `;
  }

  const text = `
    <div class=“trip-stop”>
      ${ imageString }
      <div class=“trip-stop-content”>
        <h2>${placename}</h2>
        <p class=“trip-stop-date”>${date}</p>
        ${ listString }
      </div>
    </div>
  `;

  const popup = L.popup({
    maxWidth: 400
  }).setContent(text);

  const layer = L.marker( latlng, {
    icon: L.divIcon({
      className: ‘icon’,
      html: `<span class=“icon-trip-stop”></span>`,
      iconSize: 20
    }),
    riseOnHover: true
  }).bindPopup(popup);

  return layer;
}

One thing you’ll notice as we work through this function is that we create strings of HTML text. Given that the Leaflet API we’re utilizing for this doesn’t interface directly with React, we have to build out HTML manually to pass it in to our functions.

在使用此功能时,您会注意到的一件事是,我们创建了HTML文本字符串。 鉴于我们为此目的使用的Leaflet API不能直接与React交互,我们必须手动构建HTML才能将其传递给我们的函数。

Starting from the top:

从顶部开始:

  • We take in 2 arguments, feature and latlng. Leaflet passes these 2 values in for us to use in our function.

    我们接受两个参数, featurelatlng 。 Leaflet将这两个值传递给我们以在我们的函数中使用。

  • We destructure our feature, allowing us to assign our metadata into variables

    我们对功能进行了分解,从而可以将元数据分配给变量
  • 2 string variables are initialized that we’ll use for our HTML

    初始化了两个将用于HTML的字符串变量
  • If we include a todo property as an array, we add a new list with each item inside.

    如果我们包含一个todo属性作为数组,我们将在其中添加每个项目的新列表。

  • If we include an image, we create an image tag.

    如果包含图像,则会创建一个图像标签。
  • With our newly created HTML strings, we construct the entirety of what will be our popup card for each strop

    使用我们新创建HTML字符串,我们将为每个strop构造整个弹出卡
  • With our popup HTML, we create a Leaflet popup instance

    使用我们的弹出HTML,我们创建一个Leaflet popup实例

  • With the latlng argument and our popup, we create a new Leaflet marker  instance. This will represent the point on the map.

    使用latlng参数和我们的弹出窗口,我们创建一个新的Leaflet marker实例。 这将代表地图上的点。

  • Inside of the Marker creation, we create a basic HTML tag that well use to style the marker

    在创建标记的内部,我们创建了一个基本HTML标记,该标记可用于标记的样式
  • We then bind our popup to this new Marker instance. This will allow the popup to be associated with that individual Marker

    然后,我们将弹出窗口绑定到这个新的Marker实例。 这将使弹出窗口与该单独的标记相关联
  • Finally, we return our newly created layer

    最后,我们返回新创建的图层

Remember to make sure you put all of the functions above at the bottom of your src/pages/index.js page.

请记住要确保将上面的所有功能放在src/pages/index.js页面的底部。

Once all of those functions are added, our map should still be the same thing, basically nothing happening.

一旦添加了所有这些功能,我们的地图就应该是同一件事,基本上什么也没有发生。

Follow along with the commit

跟着提交

步骤4:建立行程 (Step 4: Building our trip path)

This is where things get interesting. We’ll now utilize the functions we created to build our road trip path. All of our work here will be within the mapEffect function inside of the src/pages/index.js file.

这就是事情变得有趣的地方。 现在,我们将利用我们创建的功能来构建公路旅行路径。 我们在这里的所有工作都将在src/pages/index.js文件内的mapEffect函数内。

For context, our mapEffect function includes an argument called leafletElement. This value refers to the Map instance that Leaflet recognizes. This Map instance includes our map state as well as many utility functions to work with our map.

对于背景下,我们mapEffect功能包括一个名为参数leafletElement 。 此值是指Leaflet识别的Map实例。 这个Map实例包含我们的地图状态以及许多可与我们的地图一起使用的实用程序功能。

First, at the top of the function, we want to make sure we have a map. If not, we can return to bail out of the function.

首先,在函数顶部,我们要确保有一张地图。 如果没有,我们可以退出该功能的保释。

if ( !leafletElement ) return;

Next, we want to use the eachLayer utility function and remove each layer from our map element. We do this to make sure we always have the correct map layer state.

接下来,我们要使用eachLayer实用程序功能并从我们的map元素中删除每个layer 。 我们这样做是为了确保我们始终具有正确的地图图层状态。

leafletElement.eachLayer((layer) => leafletElement.removeLayer(layer));

With our cleaned up map, we can utilize 2 of the functions we created to create new GeoJSON objects.

通过清理后的地图,我们可以利用创建的两个功能来创建新的GeoJSON对象。

const tripPoints = createTripPointsGeoJson({ locations });
const tripLines = createTripLinesGeoJson({ locations });

With our GeoJSON objects, we need to convert those to Leaflet GeoJSON instances, which we’ll use to add to the map.

使用我们的GeoJSON对象,我们需要将其转换为Leaflet GeoJSON实例,并将其用于添加到地图中。

const tripPointsGeoJsonLayers = new L.geoJson(tripPoints, {
  pointToLayer: tripStopPointToLayer
});

const tripLinesGeoJsonLayers = new L.geoJson(tripLines);

If you notice in the above, we're using our tripStopPointToLayer function. As I alluded to before, the geoJson instance we’re creating includes a property that allows us to pass in a function, giving us the ability to manipulate the layer creation. This is how we create our point and popup content.

如果您在以上内容中注意到,我们正在使用tripStopPointToLayer函数。 正如我之前提到的,我们正在创建的geoJson实例包含一个属性,该属性允许我们传递一个函数,从而使我们能够操纵图层的创建。 这就是我们创建指向和弹出内容的方式。

We can proceed to adding both of those new layers to our map using the addTo .

我们可以使用addTo两个新图层都添加到地图中。

tripPointsGeoJsonLayers.addTo(leafletElement);
tripLinesGeoJsonLayers.addTo(leafletElement);

Next, to make sure we zoom and center on the right location, we want to grab the bounds of the map using the getBounds function on our GeoJSON layer instance.

接下来,要确保缩放并在正确的位置居中,我们想在我们的GeoJSON图层实例上使用getBounds函数来获取地图的边界。

const bounds = tripPointsGeoJsonLayers.getBounds();

Finally, we fit our map's view to those bounds using the fitBounds function on our Map instance.

最后,我们使用Map实例上的fitBounds函数使地图视图适合这些边界。

leafletElement.fitBounds(bounds);

Once you save  and reload the page, you should now see a blue path representing the jump from each of our locations on the map!

保存并重新加载页面后,您现在应该会看到一条蓝色路径,表示从地图上每个位置的跳转!

One issue though. If you notice, we only see the path. This is because we need to add some CSS which we’ll get to in the next step.

不过有一个问题。 如果您注意到,我们只会看到路径。 这是因为我们需要添加一些CSS,我们将在下一步中进行介绍。

Follow along with the commit

跟着提交

第5步:设置地图组件的样式 (Step 5: Styling our map components)

Our last step will be adding some styles that will allow our markers to show and our popups to look just right.

我们的最后一步是添加一些样式,这些样式将允许我们的标记显示并且我们的弹出窗口看起来恰到好处。

In this step, we’ll be working inside of the _home.scss file, which you can find in src/assets/stylesheets/pages.

在这一步中,我们将在_home.scss文件中工作,您可以在src/assets/stylesheets/pages找到该文件。

We can get started by copy and pasting this block of styles into the bottom of that file. With that done, we can walk through what’s happening.

我们可以通过复制此样式块并将其粘贴到该文件的底部开始。 完成此操作后,我们可以逐步了解正在发生的事情。

.trip-stop {

  width: 400px;
  overflow: hidden;

  h2 {
    font-size: 1.4em;
    margin-top: 0;
    margin-bottom: .2em;
  }

  p,
  ul,
  h3 {
    font-size: 1.2em;
    font-weight: normal;
  }

  p {
    margin: .2em 0;
  }

  .trip-stop-date {
    color: $grey-600;
    font-size: 1em;
  }

  ul {
    padding: 0 0 0 1.4em;
    margin: 0;
  }

}

.trip-stop-image {
  display: block;
  float: left;
  overflow: hidden;
  width: 150px;
  height: 150px;
  text-indent: 100%;
  color: transparent;
  background-position: center;
  background-size: cover;
}

.trip-stop-content {
  float: left;
  width: 250px;
  padding-left: 1em;
}

.icon-trip-stop {

  display: block;
  width: 1.5em;
  height: 1.5em;
  background-color: $orange-500;
  border-radius: 100%;
  box-shadow: 0 2px 5px rgba(0,0,0,.5);

  &:hover {
    background-color: $deep-orange-400;
  }

}

There’s three components to our styles above:

上面的样式包含三个组成部分:

  • .trip-stop-images: Inside of the marker popup, we optionally can include an image. These styles set the size, make the text transparent, (it’s there for accessibility), and float it to the left so that our popup content can align correctly side by side.

    .trip-stop-images :在标记弹出窗口内,我们可以选择包含图像。 这些样式设置大小,使文本透明(在此处可访问),然后将其浮动到左侧,以便我们的弹出内容可以正确并排对齐。

  • .trip-stop-content: This refers to the other half of our popup content. All we need to do here is make sure our size is appropriate and that it floats next to our image.

    .trip-stop-content :这是我们弹出窗口内容的另一半。 我们在这里需要做的就是确保我们的尺寸合适并且浮在图像旁边。

  • .icon-trip-stop: The HTML tag that we’re using as our icon designation gets styled here. We size it up, set a color using a predetermined Scss variable, and we’re good to go.

    .icon-trip-stop :在此处设置我们用作图标名称HTML标签的样式。 我们将其调整大小,使用预定的Scss变量设置颜色,我们一切顺利。

Once those styles are saved, you should now see the points on the map representing each location. Additionally, you should be able to click each of these points to open up a popup containing information about the stop.

保存这些样式后,您现在应该在地图上看到代表每个位置的点。 此外,您应该能够单击这些点中的每一个,以打开一个包含有关停靠点信息的弹出窗口。

Follow along with the commit

跟着提交

可选的最后一步:样式调整 (Optional Last Step: Style Tweaks)

The last thing that's completely optional is to make a few style tweaks to give your site a little personality. I’m not going to go over this in details, but if you’d like to follow along and dress things up a little bit, you can follow along with this commit which shows each code change I made.

完全可选的最后一件事是进行一些样式调整,使您的网站更具个性。 我将不做详细介绍,但是如果您想继续并进行一些修饰,则可以执行此提交 ,其中显示了我所做的每个代码更改。

Follow along with the commit

跟着提交

是的,我们做到了! (Yay, we did it!)

If you followed along with me, or skipped right to the starter, you should now have a mapping app that you can use for your next road trip.

如果您跟着我,或者直接跳到入门者那里,那么您现在应该拥有一个地图应用程序,可用于下一次旅行。

The good news is this project can apply to anything! Want to map out your favorite restaurants in Washington, DC? Add your locations and remove the lines. Want to create line drawings over the map? That's certainly an option.

好消息是该项目可以应用于任何事物! 想要绘制出您在华盛顿特区最喜欢的餐厅吗? 添加您的位置并删除行。 是否要在地图上创建线条图? 那当然是一个选择。

Whatever it is, if you enjoyed getting this map spun up, get creative and apply it to your next project!

无论是什么,如果您喜欢旋转地图,就可以发挥创意并将其应用于下一个项目!

想更多地了解地图? (Want to learn more about maps?)

You can check out a few of my other resources to get started:

您可以查看其他一些资源以开始使用:

翻译自: https://www.freecodecamp.org/news/how-to-create-a-summer-road-trip-mapping-app-with-gatsby-and-leaflet/

gatsby

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值