发布于 2026-01-06 1 阅读
0

使用 NASA API 创建小行星地图

使用 NASA API 创建小行星地图

市面上有很多很棒的API,可以让你处理各种有趣的数据集。如果你对太空感兴趣,那么NASA的API或许值得你了解一下。

在这篇文章中,我们将使用 NASA 的一个 API 来创建小行星地图。这将展示有多少小行星曾接近地球以及它们的大小。我们会将这些图像保存到 Cloudinary 中,以便稍后查看。

初始设置

在开始编写代码之前,我们需要准备一些事项。首先,您需要一个 NASA 小行星 - NeoWs API 的 API 密钥,我们将使用它。您可以点击此处免费获取。密钥将发送到您输入的邮箱地址。

接下来,您需要一个 Cloudinary 帐户来存储小行星地图图像,以便日后参考。您可以在这里注册一个免费帐户

我们将使用本地 Postgres 数据库,如果您还没有安装,可以从这里下载

现在这些都准备就绪,我们可以开始开发应用程序了。

创建一个新的 Redwood 项目

在终端中运行以下命令:

$ yarn create redwood-app asteroid-map
Enter fullscreen mode Exit fullscreen mode

这将为您创建许多新的文件和目录。我们将重点关注 `<head>`webapi`<head>` 文件夹。` web<head>` 文件夹用于编写所有 React 前端代码。`<head>`api文件夹用于处理 Postgres 连接和 GraphQL 后端。

创建数据库架构和连接

我们首先连接数据库并设置数据库模式。首先,打开.env项目根目录下的文件。你会看到一行被注释掉的配置项,它定义了数据库连接字符串DATABASE_URL。取消注释该行并将其更新为与你的本地连接字符串匹配。它可能类似于这样:

DATABASE_URL=postgres://postgres:admin@localhost:5432/asteroids
Enter fullscreen mode Exit fullscreen mode

您无需手动创建新数据库。首次运行迁移时,系统asteroids会自动为您创建数据库。

现在我们可以编写数据库模式了。在api > db文件夹中,打开schema.prisma. Redwood 使用Prisma来处理数据库操作。在这个文件中,我们将使用连接字符串并编写所有表的模式。

provider值从更新sqlitepostgresql。这会告诉 Primsa 我们正在使用 Postgres 实例。您可以从我们之前设置的值中看到连接字符串的读取位置DATABASE_URL

接下来,您可以删除示例模型并将其替换为以下模型:

model Map {
  id        Int    @id @default(autoincrement())
  name      String
  startDate DateTime
  endDate   DateTime
  mapUrl    String
}
Enter fullscreen mode Exit fullscreen mode

该模型代表我们将存储在数据库中的数据。NASA API 根据我们提交的日期返回小行星信息,因此我们存储这些日期是为了知道哪些日期对应于小行星地图。

运行数据库迁移

既然我们已经创建好了用于保存小行星地图的表结构,接下来就让我们运行数据库迁移。在终端中运行以下命令:

$ yarn redwood prisma migrate dev
Enter fullscreen mode Exit fullscreen mode

这将创建数据库(如果需要),然后将Map表添加到数据库中。

创建 GraphQL 类型和解析器

这就是该应用数据库端需要做的全部工作。现在我们可以转到 GraphQL 服务器了。Redwood 的 CLI 提供了许多命令,可以帮我们完成一些繁重的工作。我们将使用以下命令为后端生成类型和解析器:

$ yarn redwood generate sdl --crud map
Enter fullscreen mode Exit fullscreen mode

这将为我们生成多个文件,用于处理地图的所有增删改查(CRUD)功能。我们只需要添加从 NASA API 获取的数据类型以及用于获取这些数据的解析器即可。

添加小行星数据类型

api > src > graphql目录中,打开新生成的maps.sdl.ts文件。该文件已经包含了我们可能用于更新数据库的 CRUD 查询和变更的类型定义。

现在,我们将添加类型来定义从 API 获取的数据类型、发送到 API 的输入类型以及用于返回数据的查询语句。在类型下方Map,添加以下代码:

type Asteroid {
  missDistance: String
  estimatedDiameter: String
}

input AsteroidInput {
  startDate: Date!
  endDate: Date!
  viewDate: Date!
}

type Query {
  asteroids(input: AsteroidInput): [Asteroid] @requireAuth
  maps: [Map!]! @requireAuth
  map(id: Int!): Map @requireAuth
}
Enter fullscreen mode Exit fullscreen mode

这样我们就能访问查询语句及其所需信息。接下来,我们来定义解析器以获取这些数据。

通过解析器调用 NASA API

这是GraphQL的一大优点。你可以在解析器中调用另一个API,数据会通过同一个端点发送,就像它访问你自己的数据库一样。

api > src > services > maps打开该文件。其中maps.js包含了我们之前运行的 CLI 命令创建的 CRUD 解析器。在所有这些解析器下方,添加以下解析器以获取小行星数据:

export const asteroids = ({ input }) => {
  return fetch(`https://api.nasa.gov/neo/rest/v1/feed?start_date=${input.startDate.toISOString().split('T')[0]}&end_date=${input.endDate.toISOString().split('T')[0]}&api_key=${your_api_key_really_goes_here}`)
  .then(response => {
    return response.json()
  })
  .then(rawData => {
    const data = rawData.near_earth_objects[input.viewDate.toISOString().split('T')[0]]

    const asteroids = data.map(value => {
      return {
        missDistance: value.close_approach_data[0].miss_distance.kilometers,
        estimatedDiameter: value.estimated_diameter.kilometers.estimated_diameter_max
      }
    })

    return asteroids
  })
}
Enter fullscreen mode Exit fullscreen mode

这个解析器接收我们传递给它的输入,并向 API 发出请求。与许多 API 请求一样,我们必须以特定格式发送输入。这就是我们以这种方式拆分日期字符串的原因。GraphQL 传递的日期格式与 NASA API 的格式不兼容。

然后我们从响应中获取数据,并检查传入日期附近的小行星viewDate。这个日期可以是起始日期和结束日期之间的任何时间。我们获取 API 返回的数据,提取所需的值,并将这些值作为成功响应的一部分传递。

后端部分到此结束!我们已经拥有获取小行星数据并将其保存到数据库所需的所有类型和解析器。现在我们可以把注意力转向前端,完成最后的收尾工作了。

构建用户界面

让我们直接进入正题。我们需要安装一个软件包才能保存我们创建的小行星地图。在终端中,进入该web目录并运行:

$ yarn add html-to-image
Enter fullscreen mode Exit fullscreen mode

这将使我们能够非常快速地获取小行星地图的图像。

我们可以使用 Redwood CLI 来生成小行星地图页面。因此,请在终端中返回项目根目录并运行以下命令:

$ yarn redwood generate page asteroid
Enter fullscreen mode Exit fullscreen mode

这将更新Routes.tsx文件路径,并生成一些新文件web > src > pages > AsteroidPage。我们将要操作的文件是 ` AsteroidPage.tsx.import`。打开此文件,删除所有现有的导入语句,并将其替换为以下内容:

import { useQuery, useMutation } from '@redwoodjs/web'
import { useState, useRef } from 'react'
import { toPng } from 'html-to-image'
Enter fullscreen mode Exit fullscreen mode

导入这些数据后,我们可以添加 GraphQL 查询来获取小行星数据,并添加 mutation 将地图保存到 Cloudinary 和数据库中。

const CREATE_MAP_MUTATION = gql`
  mutation CreateMapMutation($input: CreateMapInput!) {
    createMap(input: $input) {
      id
    }
  }
`

const GET_ASTEROIDS = gql`
  query GetAsteroids($input: AsteroidInput!) {
    asteroids(input: $input) {
      missDistance
      estimatedDiameter
    }
  }
`
Enter fullscreen mode Exit fullscreen mode

在组件中添加状态和使用钩子

所有导入和GraphQL定义都已就绪,让我们开始在AsteroidPage组件内部工作。您可以删除组件中的所有内容,因为我们将编写很多不同的代码。

我们将首先添加组件所需的状态和其他钩子。

const [createMap] = useMutation(CREATE_MAP_MUTATION)

const canvasRef = useRef(null)

const [startDate, setStartDate] = useState("2021-08-12")
const [endDate, setEndDate] = useState("2021-08-15")
const [viewDate, setViewDate] = useState("2021-08-13")

const { loading, data } = useQuery(GET_ASTEROIDS, {
  variables: { input: { startDate: startDate, endDate: endDate, viewDate: viewDate }},
})
Enter fullscreen mode Exit fullscreen mode

首先,我们创建用于向数据库添加新记录的变更方法。然后,我们设置用于保存小行星地图图像的画布引用。接下来,我们设置几个不同的日期状态。这些设置将允许我们调整保存的地图内容以及在应用程序中显示的内容。

接下来是数据获取查询。它会调用我们之前创建的解析器,从 NASA API 获取小行星数据。我们传入的参数格式与input我们在后端类型中定义的格式相同。这些值来自状态,因此每当状态值发生变化时,我们都可以获取新的小行星地图。

处于加载状态

你会注意到我们loadinguseQuery调用中获取了一个值。这告诉我们数据是否仍在获取中。添加一个元素来告知用户页面正在加载非常重要。这也能防止在数据尚未可用时应用程序崩溃。因此,在数据查询下方添加以下代码:

if (loading) {
  return <div>Loading...</div>
}
Enter fullscreen mode Exit fullscreen mode

这只会在页面上显示加载信息。

渲染的元素

现在数据已经传入,让我们编写返回语句来决定页面上应该渲染什么内容。在加载状态检查下方,添加以下代码,我们稍后会详细介绍:

return (
  <>
    <h1>AsteroidPage</h1>
    <form onSubmit={submit}>
      <div>
        <label htmlFor="mapName">Map Name</label>
        <input type="text" name="mapName" />
      </div>
      <div>
        <label htmlFor="startDate">Start Date</label>
        <input type="date" name="startDate" />
      </div>
      <div>
        <label htmlFor="endDate">End Date</label>
        <input type="date" name="endDate" />
      </div>
      <div>
        <label htmlFor="viewDate">View Date</label>
        <input type="date" name="viewDate" />
      </div>
      <button type="submit">Save Asteroid Map</button>
    </form>
    <button type="button" onClick={makeAsteroidMap}>View Map</button>
    <canvas id="asteroidMap" ref={canvasRef} height="3000" width="3000"></canvas>
  </>
)
Enter fullscreen mode Exit fullscreen mode

其实并没有看起来那么复杂。我们有一个表单,里面有一些输入框,用于填写小行星地图的名称以及获取数据和图像所需的日期。表单上有一个提交按钮,点击后会根据我们输入的信息获取新的小行星数据并保存新地图。

还有一个按钮,可以让我们在它下面的 canvas 元素中查看小行星地图。canvas 元素就是我们useRef上面钩子函数的目标元素。表单按钮和查看地图按钮都有我们需要编写的功能。

如果你想查看目前为止的应用程序运行情况,请yarn redwood dev在终端中运行。你应该会看到类似这样的内容。

小行星数据输入的基本用户表单

提交功能

我们将把这个函数添加到加载状态检查的正下方。它会获取表单数据,更新日期状态,拍摄画布中小行星地图的快照,将其上传到 Cloudinary,然后创建一个新的数据库记录。

async function submit(e) {
  e.preventDefault()
  const mapName = e.currentTarget.mapName.value
  const startDate = e.currentTarget.startDate.value
  const endDate = e.currentTarget.endDate.value
  const viewDate = e.currentTarget.viewDate.value

  setStartDate(startDate)
  setEndDate(endDate)
  setViewDate(viewDate)

  if (canvasRef.current === null) {
    return
  }

  const dataUrl = await toPng(canvasRef.current, { cacheBust: true })

  const uploadApi = `https://api.cloudinary.com/v1_1/${cloudName}/image/upload`

  const formData = new FormData()
  formData.append('file', dataUrl)
  formData.append('upload_preset', upload_preset_value)

  const cloudinaryRes = await fetch(uploadApi, {
    method: 'POST',
    body: formData,
  })

  const input = {
    name: mapName,
    startDate: new Date(startDate),
    endDate: new Date(endDate),
    mapUrl: cloudinaryRes.url
  }

  createMap({
    variables: { input },
  })
}
Enter fullscreen mode Exit fullscreen mode

您需要cloudName从 Cloudinary 控制台获取并上传预设值。剩下的唯一一个需要编写的函数就是在画布上绘制小行星地图的函数。

绘制小行星图

这将在距离页面左侧不同距离处创建大小不同的圆圈,以显示它们与地球的距离以及它们的大小。

function makeAsteroidMap() {
  if (canvasRef.current.getContext) {
    let ctx = canvasRef.current.getContext('2d')

    data.asteroids.forEach((asteroid) => {
      const scaledDistance = asteroid.missDistance / 75000
      const scaledSize = asteroid.estimatedDiameter * 100
      let circle = new Path2D()

      circle.arc(scaledDistance * 2, scaledDistance, scaledSize, 0, 2 * Math.PI)

      ctx.fill(circle)
    })
  }
}
Enter fullscreen mode Exit fullscreen mode

这里的缩放比例并没有基于任何特定依据,所以请随意尝试不同的计算方法!

现在,如果您运行该应用程序并单击“查看地图”按钮,您将看到类似这样的内容。

小行星地图

如果更新日期,就可以查看不同的地图并将其保存到数据库中。这就是这个应用程序的全部代码!

现在你可以看到我们每天都离小行星撞击事件有多近。

已完成的代码

asteroid-map您可以在此仓库的文件夹中查看完整的项目。或者,您也可以在此代码沙箱中查看前端代码。为了使其正常工作,您需要更新一些值以匹配您自己的值。

结论

我们经常需要使用外部 API,而 GraphQL 正是我们集中管理所有调用 API 的方法之一。用它来可视化呈现我们每天与小行星撞击的距离,只是 GraphQL 功能的一个小小趣味用法。

文章来源:https://dev.to/flippedcoding/creating-an-asteroid-map-with-a-nasa-api-4afc