以下是通过名为WRLD的API进行的…

本文由WRLD 3D赞助。 感谢您支持使SitePoint成为可能的合作伙伴。

以下活动在圣诞节前夕的7:00 am至8:00 am之间进行。 事件是实时发生的。

对于我们所有的数据收集功能,在可视化我们所居住的3D世界中的数据时,我们仍然抱有希望。我们盯着2D图表和日志条目,但是我们从世界中拔出的许多数据都具有意义在3D环境中。 并且,当将数据重新应用到3D模型中时,可视化该数据可能会很有用。

这是增强现实寻求解决的问题。 与虚拟现实的虚构环境相比,增强现实可以帮助我们解决许多现实问题。 通过应用数据,否则我们将通过2D媒体将其消耗到我们周围的现实世界中。 映射是增强现实的孩子中的长子。

当WRLD与我们联系时,写他们的平台时,我立即被他们平台的图形和性能所吸引。 但是,我越是使用他们的平台; 我对API的实用性和映射数据的保真度更加着迷。

我们将发布一系列教程,以演示如何使用该平台将信息带入适用的世界。 每个教程的主题均根据受欢迎的电视节目而定。 您可能已经猜到,第一个大约是24

在本教程中,我们将学习如何开始使用WRLD平台。 我们将按照文档示例来呈现最简单的地图。 然后,我们将创建一个本地环境来编译代码。 并开始讲一个故事。

我们将讨论以下主题:

  • 根据地点名称渲染地图
  • 遍历地图,了解一系列事件
  • 突出建筑物并在每个建筑物上设计活动
  • 使用HTML5音频API播放声音文件
  • 更改天气状况和一天中的时间

可以在Github找到本教程的代码。 它已经过现代版本或Firefox,Node和macOS的测试。

入门

最简单的入门方法是遵循文档中的第一个示例。 在此之前,我们需要一个帐户。 转到https://www.wrld3d.com ,然后单击“注册”。

WRLD主页

登录后,单击“开发人员”和“访问API密钥”。

WRLD访问API密钥屏幕

为您的应用程序创建一个新的API密钥。 您可以调用任何名称,但是稍后需要复制生成的密钥…

WRLD密钥

对于第一个示例,我们可以从官方文档站点获得代码。 我将其放在CodePen中,并用纽约的坐标替换了坐标:

WRLD.js基于Leaflet.js ,这使以前做过一些基于地图的工作的任何人都熟悉。 这也意味着地图具有移动友好性和交互性。

使用鼠标左键单击并拖动以在地图上平移。 用鼠标右键单击并拖动以旋转地图。 单击并拖动,用鼠标中键更改视角。 滚动鼠标滚轮将影响缩放。 该地图还可以在触摸设备上进行控制。

免费学习PHP!

全面介绍PHP和MySQL,从而实现服务器端编程的飞跃。

原价$ 11.95 您的完全免费

除了包括Javascript SDK和样式表之外; 我们只需要大约5行格式化代码即可绘制出精美的纽约地图! 第一个参数map是WRLD将地图渲染到的元素的ID。 第二个是我们生成的API密钥。 第三个是配置对象。 该对象包含地图中心的坐标,以及可选的缩放级别。

建立构建链

CodePen非常适合进行快速演示。 但是我们需要更强大,更美观的东西。 让我们设置一些简单的东西,它将所有现代Javascript编译成大多数浏览器可以理解的版本。

ParcelJS是最近宣布的; 作为快速的零配置Web捆绑器。 让我们测试一下。 首先,我们需要通过NPM将Parcel安装为全局应用程序:

npm install -g parcel-bundler

接下来,我们可以为项目创建一些文件。 我们需要一个Javascript文件,一个CSS文件和一个HTML入口点文件:

const Wrld = require("wrld.js")

const map = Wrld.map("map", "[your API key here]", {
    center: [40.73061, -73.935242],
    zoom: 16,
})

这是来自tutorial/app.js

@import "https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.0.1/leaflet.css";

html,
body {
    margin: 0;
    padding: 0;
    width: 100%;
    height: 100%;
}

#map {
    width: 100%;
    height: 100%;
    background-color: #000000;
}

这是来自tutorial/app.css

<!doctype html>
<html lang="en">
    <head>
        <meta charset="utf-8" />
        <link rel="stylesheet" href="./app.css" />
        <title>Getting started with WRLD</title>
    </head>
    <body>
        <div id="map"></div>
        <script src="./app.js"></script>
    </body>
</html>

这是来自tutorial/index.html

注意app.js如何要求wrld.js吗? 我们需要安装WRLD Javascript SDK:

npm init -y
npm install --save wrld.js

然后,我们可以开始使用Parcel构建和运行本地文件:

parcel index.html

这将启动本地开发服务器,并捆绑JS和CSS文件。 该过程如下所示:

包裹包裹

打开浏览器中显示的URL,您应该再次看到纽约地图。 当我们更改JS和CSS文件时,这些文件将自动重新编译并重新加载到浏览器中。 包裹似乎确实符合其要求。

而且,这正是我们所需要的—省力的构建链,使我们能够专注于使用WRLD完成工作!

包裹仍然很新。 您可能难以满足高度定制的工作流程或构建要求; 并且文档仍然可以解释这些情况下的处理方法。 不过,我认为这个简单的构建链将满足我们的需求,Parcel在这里已兑现了诺言。

将名称转换为坐标

有时,我们知道我们要考虑的位置的确切坐标。 有时我们只知道地点的名称。 让我们快速回避,看看当我们只知道它的名字时如何发现该位置的坐标。

这是WRLD平台上尚不可用的少数服务之一。 因此,让我们使用Google API进行计算。 我们需要另一个API密钥,因此请转到https://developers.google.com/maps/documentation/geocoding/get-api-key并单击“获取密钥”:

写谷歌密钥

接下来,我们可以使用Google Geocoding服务,通过稍微更改Javascript来查找地址的坐标:

const Wrld = require("wrld.js")

const keys = {
    wrld: "[your WRLD API key]",
    google: "[your Google API key]",
}

window.addEventListener("load", async () => {
    const address = encodeURIComponent("empire state building, new york")

    const endpoint = "https://maps.googleapis.com/maps/api/geocode/json?"
        + "key=" + keys.google + "&address=" + address

    // console.log(endpoint)

    const response = await fetch(endpoint)
    const lookup = await response.json()

    // console.log(lookup)

    const { lat, lng } = lookup.results[0].geometry.location

    const map = Wrld.map("map", keys.wrld, {
        center: [lat, lng],
        zoom: 12,
    })
})

这是来自tutorial/app.js

我已经将键重构为一个对象。 我们甚至可以将它们移到环境变量文件中,并从Git中排除该文件。 这样,密钥可能有用但对公众隐藏。 我还将代码移动到异步短箭头函数中,以便可以使用asyncawait ; 这样一来,一旦加载文档,它就会发生。

接下来,我们可以定义一个要查找的地址。 最好对地址进行编码,以便可以将其用作查询字符串参数。 我们可以将其与Google API密钥一起馈入地理编码API端点,以获取结果。

继续并取消注释控制台日志语句,这样您就可以看到编码后的URI的样子,以及Google返回给我们的结果。 我们从Google获得了相当详细的结果,但是我们想要的位在results[0].geometry.location 。 使用对象解构,我们可以仅提取该对象的latlng键。

最后,我们可以将其输入到map函数中,地图将渲染帝国大厦。 就像我说的,我们经常已经知道地图中心的坐标。 但是,当我们不这样做时:此服务和代码将帮助我们找到它们。

在地图上移动

让我们开始进行沉浸式地图体验。 我们希望带某人进行一系列事件,然后将地图移至每个新事件,以便我们向他们讲述一个故事。 将故事内容与故事机制分开的一种好方法是创建一个单独的“数据” JavaScript导入:

module.exports = [
    {
        // start at Empire State Building
        lat: 40.7484405,
        lng: -73.98566439999999,
        seconds: 15,
        image: ".jack-1",
        text: "What a lovely day...<phone rings>",
    },
    {
        // stay in the same place but update story
        lat: 40.7484405,
        lng: -73.98566439999999,
        seconds: 15,
        image: ".chloe-1",
        text: "Jack, we have a problem...",
    },
    // ...more events
]

这是来自tutorial/story.js

我们可以将故事分为地图事件。 每个事件甚至都有latlng ,尽管某些事件可能在先前的位置发生。 对于每个事件,我们都会显示某人讲话的图片以及他们在说什么。 几秒钟后,我们会将相机移至新位置和/或扬声器。

我们可以将该文件导入到主要的Javascript文件中,并更改地图以显示第一个故事事件。 我们甚至可以突出显示事件发生的建筑物:

const story = require("./story")

window.addEventListener("load", async () => {
    // ...old code commented out here

    const { lat, lng } = story[0]

    const map = Wrld.map("map", keys.wrld, {
        center: [lat, lng],
        zoom: 15,
    })

    map.on("initialstreamingcomplete", () => {
        Wrld.buildings
            .buildingHighlight(
                Wrld.buildings
                    .buildingHighlightOptions()
                    .highlightBuildingAtLocation([lat, lng])
                    .color([125, 255, 125, 128]),
            )
            .addTo(map)
    })
})

这是来自tutorial/app.js

该代码演示了初始地图渲染/流式处理完成后如何突出显示建筑物。 Wrld.buildings.buildingHighlightOptions创建一个模板选项对象,向其中添加突出显示的位置和颜色。 我们将此选项对象传递给Wrld.buildings.buildingHighlight以创建高光,并将其添加到地图。 颜色数组是RGBA值,这意味着第四个整数是不透明度值( 128约为255限制的一半,或50%透明)。

这不是突出建筑物的唯一方法。 我们还可以使用射线投射来选择建筑物,但这比我们这里需要的要先进。 您可以在https://wrld3d.com/wrld.js/latest/docs/api/L.Wrld.buildings.BuildingHighlightOptions中找到有关它的文档。

实际上,当我们这样做时; 我们可以将建筑物的亮点抽象为可重用的功能。 我们甚至可以为每个事件添加特定的突出显示颜色,并在每次添加新事件时删除以前的建筑物突出显示:

const { lat, lng, color } = story[0]

const map = Wrld.map("map", keys.wrld, {
    center: [lat, lng],
    zoom: 15,
})

map.on("initialstreamingcomplete", () => {
    highlightBuildingAt(lat, lng, color)
})

let highlight = null

const highlightBuildingAt = (lat, lng, color) => {
    if (highlight) {
        highlight.remove()
    }

    highlight = Wrld.buildings
        .buildingHighlight(
            Wrld.buildings
                .buildingHighlightOptions()
                .highlightBuildingAtLocation([lat, lng])
                .color(color),
        )
        .addTo(map)
}

这是来自tutorial/app.js

这样,Jack和Chloe可以拥有自己的突出显示颜色,以在说话时显示出来。 删除建筑物高光比添加高光更容易。 我们只需要保存对我们创建的突出显示的引用,并在其上调用remove方法。

移动地图

好的,现在我们需要将地图移至每个新事件。 我们将突出显示每个事件的建筑物,因此我们知道我们正在查看哪个事件:

const { lat, lng, zoom, color, seconds } = story[0]

const map = Wrld.map("map", keys.wrld, {
    center: [lat, lng],
    zoom,
})

map.on("initialstreamingcomplete", () => {
    highlightBuildingAt(lat, lng, color)

    if (story.length > 1) {
        setTimeout(() => showNextEvent(1), seconds * 1000)
    }
})

let highlight = null

const highlightBuildingAt = (lat, lng, color) => {
    if (highlight) {
        highlight.remove()
    }

    highlight = Wrld.buildings
        .buildingHighlight(
            Wrld.buildings
                .buildingHighlightOptions()
                .highlightBuildingAtLocation([lat, lng])
                .color(color),
        )
        .addTo(map)
}

const showNextEvent = index => {
    const { lat, lng, zoom, degrees, color, seconds } = story[index]

    map.setView([lat, lng], zoom, {
        headingDegrees: degrees,
        animate: true,
        durationSeconds: 2.5,
    })

    setTimeout(() => {
        highlightBuildingAt(lat, lng, color)

        if (story.length > index + 1) {
            setTimeout(() => showNextEvent(index + 1), seconds * 1000)
        }
    }, 2.5 * 1000)
}

这是来自tutorial/app.js

这里有很多事情,所以让我们分解一下:

  1. 我们向每个事件添加了zoom属性。 这意味着我们可以对事件之间的缩放级别进行动画处理,从而使故事更具动感。 除了第一个事件外,我们还为所有事件添加了一个degrees属性。 我们可以修改第一个事件的摄影机方向,但默认情况下(360度)我觉得很好。 在事件中添加度数可以使标题动画与缩放动画大致相同。
  2. 如果存在多个事件(可以放心地假设这样做,但是无论如何我都添加了检查),那么我们将使用第一个事件的seconds属性来延迟向事件2的过渡。 我们创建一个showNextEvent函数,其硬编码索引值为1
  3. showNextEvent ,我们使用setView方法对摄像机的位置,缩放和航向进行动画处理。 动画将花费2.5秒,因此我们将超时设置为该时间。 在超时回调函数中,我们突出显示新建筑物(以便突出显示仅在摄像机完成移动之后发生)并排队下一个事件。

随时添加更多事件和/或完全更改故事。 自己制作,玩得开心!

添加音频

我们的故事有点安静。 我们需要一些悬念的背景音乐才能进入该区域。 前往Epidemic Sound之类的网站,并为您的故事找到一些可疑的音乐曲目。 我已经下载了一些文件,并将它们放在了tutorial/tracks文件夹中。

现在,让我们创建一个不可见的音频播放器,并使其随机播放曲目。 为此,我们需要一个曲目列表:

<!doctype html>
<html lang="en">
    <head>
        <meta charset="utf-8" />
        <link rel="stylesheet" href="./app.css" />
        <title>Getting started with WRLD</title>
    </head>
    <body>
        <div id="map"></div>
        <audio class="track-1" src="./tracks/track-1.mp3" />
        <audio class="track-2" src="./tracks/track-2.mp3" />
        <audio class="track-3" src="./tracks/track-3.mp3" />
        <audio class="track-4" src="./tracks/track-4.mp3" />
        <audio class="track-5" src="./tracks/track-5.mp3" />
        <audio class="track-6" src="./tracks/track-6.mp3" />
        <audio class="track-7" src="./tracks/track-7.mp3" />
        <audio class="track-8" src="./tracks/track-8.mp3" />
        <audio class="track-9" src="./tracks/track-9.mp3" />
        <audio class="track-10" src="./tracks/track-10.mp3" />
        <script src="./app.js"></script>
    </body>
</html>

这是来自tutorial/index.html

Parcel正在监视index.html并将所有静态文件引用重写为其复制到dist文件夹中的文件。 如果我们在此HTML文件中创建HTML4 audio标签,Parcel会将这些文件复制到列表文件夹,然后通过开发服务器提供这些文件。 我们不必以这种方式做事,但是随着我们的开发,它对于测试来说更加简单。

一种替代方法是从Internet上的某个地方引用这些文件。 另一个可能是不使用开发服务器。

module.exports = [
    ".track-1",
    ".track-2",
    ".track-3",
    ".track-4",
    ".track-5",
    ".track-6",
    ".track-7",
    ".track-8",
    ".track-9",
    ".track-10",
]

这是来自tutorial/tracks.js

我们可以使用此列表查找与要播放的每个*.mp3文件链接HTML元素。 我们将在主要的JS文件中使用此列表:

const nextTrack = () => {
    const index = Math.floor(Math.random() * tracks.length)

    const audio = new Audio(document.querySelector(tracks[index]).src)
    audio.addEventListener("ended", () => nextTrack())
    audio.play()
}

nextTrack()

这是来自tutorial/app.js

我们想随机播放曲目,因此我们找到一个随机索引。 然后,获取与该索引匹配的audio元素,并使用其src属性值创建一个新的Audio对象。 曲目播放完毕后,我们再次调用nextTrack函数(因此,下一个随机曲目将开始循环播放)并开始随机选择的曲目。

不幸的是,我无法在Github存储库中包含正在使用的曲目。 首先,它们将极大地增加仓库的规模。 其次,我有权将其用于YouTube作品,但不得出于任何其他原因进行分发。 如果要获取我使用过的曲目,可以在“ 流行声音”搜索结果页面上找到它们。

为活动添加信息卡

我之前提到过; WRLD.js基于LeafletJS。 这很棒,因为在处理WRLD映射时,我们可以做Leaflet允许的所有事情。 实际上,我们可以使用Leaflet弹出窗口来叙述故事事件。 Leaflet弹出窗口如下所示:

L.popup()
    .setLatLng(latlng)
    .setContent("I am a popup!")
    .openOn(map)

我们将在弹出窗口中嵌入每个事件的图像和文本。 如果我们可以将弹出窗口相对于建筑物的高度定位,那也很酷。 不是在顶部,而是……说……沿着建筑物的一半。 我们可以这样使用:

let popup = null

const showPopup = (lat, lng, image, text, elevation) => {
    const src = document.querySelector(image).src

    const element1 = "<img class='image' src='" + src + "' />"
    const element2 = "<span class='text'>" + text + "</span>"
    const element3 = "<div class='popup'>" + element1 + element2 + "</div>"

    popup = L.popup({
        closeButton: false,
        autoPanPaddingTopLeft: 100,
        elevation: Math.max(20, elevation / 2),
    })
        .setLatLng(L.latLng(lat, lng))
        .setContent(element3)
        .openOn(map)
}

这是来自tutorial/app.js

L.popup接受一个options对象。 我们设置的选项是:

  1. 我们想要隐藏通常在Leaflet弹出窗口上显示的关闭按钮。
  2. 当摄像机完成平移以显示弹出窗口时,我们希望摄像机在屏幕的顶部/左侧之间留出足够的空间。
  3. 我们希望弹出窗口距离地面至少20米,并且最多为建筑物其他高度的一半。

我们还在构造一个HTML字符串。 它将事件的图像和文本放在.popup元素内。 我们可以对这些元素使用以下样式:

.hidden {
    display: none;
}

.image {
    display: flex;
    width: auto;
    height: 100px;
}

.text {
    display: flex;
    padding-left: 10px;
    font-size: 16px;
}

.popup {
    display: flex;
    flex-direction: row;
    align-items: flex-start;
}

这是来自tutorial/app.css

.popup是Flexbox容器元素。 我们应用于它的flex样式是,子级应该连续显示,并且它们应该与容器的顶部对齐。 有很多很棒的Flexbox指南。 看看Flexbox Zombies ,这是一种有趣的学习方式……

请注意,我们还为index.html的图像定义了.hidden样式。 我们不希望它们显示-它们在那里,因此Parcel将正确复制和引用它们。

问题是:我们如何获得每座建筑物的标高? 我们可以听取建筑信息事件,并从那里算出海拔。 不幸的是,没有突出显示此方法的方法,因此我们必须陷入“全局”事件并偷偷地添加/删除侦听器:

let elevation = 0

const waitForElevation = onElevation => {
    const listener = event => {
        map.buildings.off("buildinginformationreceived", listener)

        const information = event.buildingHighlight.getBuildingInformation()

        if (!information) {
            onElevation(0)
        } else {
            const dimensions = information.getBuildingDimensions()
            const ground = dimensions.getBaseAltitude()
            const elevation = dimensions.getTopAltitude() - ground

            onElevation(elevation)
        }
    }

    map.buildings.on("buildinginformationreceived", listener)
}

这是来自tutorial/app.js

waitForElevation创建并将侦听器功能添加到buildinginformationreceived地图事件。 触发侦听器的那一刻,它会自行删除。 这样,我们可以触发每个事件:添加侦听器→突出显示建筑物→调用侦听器→删除侦听器。

收到的buildinginformationreceived会接收一个事件,该事件具有getBuildingInformation方法。 如果建筑物有任何信息,我们将获取地面海拔高度并据此计算出海拔高度。 如果不是,我们调用onElevation函数参数。 因此, onElevation会以0或更大的整数来调用。

所有剩下要做的就是一个加onElevation回调到每个highlightBuildingAt通话; 并在该函数内调用waitForElevation

map.on("initialstreamingcomplete", () => {
    highlightBuildingAt(
        lat, lng, color,
        elevation => showPopup(lat, lng, image, text, elevation)
    )

    if (story.length > 1) {
        setTimeout(() => showNextEvent(1), seconds * 1000)
    }
})

let highlight = null

const highlightBuildingAt = (lat, lng, color, onElevation) => {
    waitForElevation(onElevation)

    // ...rest of highlightBuildingAt
}

const showNextEvent = index => {
    // ...rest of showNextEvent

    setTimeout(() => {
        highlightBuildingAt(
            lat, lng, color,
            elevation => showPopup(lat, lng, image, text, elevation)
        )

        if (story.length > index + 1) {
            setTimeout(() => showNextEvent(index + 1), seconds * 1000)
        }
    }, 2.5 * 1000)
}

这是来自tutorial/app.js

改变天气和一天中的时间

杰克的故事在冬天播出。 但是地图晴朗而明亮。 让我们将天气更改为与季节保持一致:

map.themes.setWeather(Wrld.themes.weather.Snowy)

这是来自tutorial/app.js

改变天气很容易。 在这里,我们正在下雪; 但是我们可以做到以下任何一种:

  • Wrld.themes.weather.Clear
  • Wrld.themes.weather.Overcast
  • Wrld.themes.weather.Foggy
  • Wrld.themes.weather.Rainy
  • Wrld.themes.weather.Snowy

同样,我们希望使时间流逝更加现实。 每24集应该在1小时内发生一次。 如果我们可以将每个位置相隔1小时,那就太好了,但我们只有以下时间可以配合使用:

  • Wrld.themes.time.Dawn
  • Wrld.themes.time.Day
  • Wrld.themes.time.Dusk
  • Wrld.themes.time.Night

让我们根据每个事件更改一天中的时间:

const { lat, lng, zoom, color, seconds, image, text, time } = story[0]

const map = Wrld.map("map", keys.wrld, {
    center: [lat, lng],
    zoom,
})

if (time) {
    map.themes.setTime(time)
}

// ...later

const showNextEvent = index => {
    const {
        lat, lng, zoom, degrees, color, seconds, image, text, time
    } = story[index]

    map.setView(...)

    setTimeout(() => {
        if (time) {
            map.themes.setTime(time)
        }

        highlightBuildingAt(...)

        if (story.length > index + 1) {
            setTimeout(...)
        }
    }, 2.5 * 1000)
}

这是来自tutorial/app.js

摘要

今天完成了。 希望您以后能像我把它们放在一起一样开心。 花一些时间修饰你的故事; 添加新角色,新音乐以及您认为可以使您的故事精彩的任何内容。 我们很乐意看到您的想法。

这是最终产品的视频。 我将为此添加更多事件,但是我为我们的管理感到骄傲:

下次,我们将学习有关WRLD平台允许的演示,动画和自动化的更多信息。 实际上,我们将使用WRLD创建有用的,可出售的移动友好型应用程序。 下次见!

翻译自: https://www.sitepoint.com/building-dynamic-3d-maps/

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值