使用WRLD构建George Costanza的浴室搜索器

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

“在城市的任何地方? 在城市的任何地方:我会告诉你最好的公共厕所。”

这是乔治·科斯坦萨杰里·塞恩菲尔德在1991年宋飞的那个情节的话; 有远见的乔治在自己的时间之前发明了一个应用程序-浴室取景器! 如果您是经常旅行的人,父母或只是知道清洁和维护良好的空间对于某些“宁静”的重要性的人,您将了解该想法的实用性。

乔治·科斯坦扎-宏伟的设施

因此,这一次在WRLD系列的第二篇教程中,我们将构建一个……我们称之为“设施查找器应用程序”。

我们的设施查找器应用程序预览 窥视我们将要共同构建的内容

这不是有人第一次尝试这种想法。 在2010年,bathroomreview.ca做到了这一点(如《福布斯》中所述 )。 但是该站点不再可用。

在本轮的上一教程中,我们涵盖了相当多的领域,我们将重用其中的一些知识。 例如,我们将使用ParcelJS来构建我们的静态文件,但是我们不会过多地介绍如何重新设置它。 我们还将突出显示建筑物,并根据用户的需求设置适当的天气条件和一天中的时间。 如果不确定这些方法的工作原理,请返回上一教程

在本教程中,我们将介绍以下主题:

  • 创建一个简单的AdonisJS服务器端API(以缓存位置数据并处理CORS请求)。
  • 如果距离用户10米之内没有缓存的位置,请从refugerestrooms.org请求公共设施数据。 我们将使用Google Distance Matrix API来计算兴趣点之间的距离。
  • 突出显示具有公共设施的建筑物,并对其颜色进行着色以使其达到其等级。 绿色代表好,红色代表坏。 每个建筑物都会有一张信息卡,以提供更多信息(例如如何到达浴室)。

最后,我们将讨论如何将这种应用程序变成可行的业务。 这真的是重点吗? WRLD API提供了在真实世界地图中可视化真实世界数据的工具。 我们的工作是研究如何将此技术用于商业应用!

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

获取设施数据

让我们从学习如何获取设施数据以及获取数据的形式开始。我们将使用refugerestrooms.org作为数据源。 我们了解到可以通过查看文档按纬度和经度进行搜索。 实际上,我们可以发出以下请求,并查看我所在地附近的一组设施:

curl https://www.refugerestrooms.org/api/v1/restrooms/by_location.json? ↵
    lat=-33.872571799999996&lng=18.6339362

我们可以指定其他一些参数(例如是否通过可访问和/或男女通用的设施进行过滤),但是这给我们提供的主要功能是一种将坐标插入搜索并获得附近位置的方法。

但是,我们不能仅仅从浏览器中调用它。 出于种种安全原因,不允许这样做。 也有性能原因。 如果10个人提出相同的要求,并且彼此间隔10米,该怎么办? 如果我们可以从缓存代理中更快地向它发出10个请求,那将是浪费时间。

相反,我们将建立一个简单的AdonisJS缓存API。 我们的浏览器应用程序将向AdonisJS API发送请求,如果没有“附近”的数据,则将其发送; 它将向Refuge API发送请求。 我们不能花太多时间在AdonisJS的细节上,因此您必须查看文档以获取详细信息。

我也即将写一本有关它的书 ,所以这是学习它的最佳方式!

创建新的AdonisJS应用最简单的方法是安装命令行工具:

npm install --global @adonisjs/cli

这将全局启用adonis命令行。 我们可以使用它来创建新的应用程序框架:

adonis new proxy

这需要一些时间,因为它已安装了一些东西。 完成后,您应该会看到一条消息,以运行开发服务器。 这可以通过以下方式完成:

adonis serve --dev

在浏览器中打开http://127.0.0.1:3333,您会被这种美丽所吸引:

阿多尼斯欢迎画面

创建迁移和模型

让我们讲一下数据库中的搜索数据。 AdonisJS支持几种不同的引擎,但是为了简单起见,我们将使用SQLite。 我们可以使用以下方法安装适当的驱动程序:

npm install --save sqlite3

接下来,让我们进行迁移和建模。 我们只对用于搜索的坐标以及返回的JSON感兴趣。 如果坐标足够接近用户要搜索的位置,我们将重用现有的搜索响应,而不是重新请求搜索数据。

我们可以使用adonis命令行实用程序来创建迁移和模型:

adonis make:migration search
adonis make:model search

这样就创建了几个文件。 第一个是迁移,我们可以在其中添加三个字段:

"use strict"

const Schema = use("Schema")

class SearchSchema extends Schema {
    up() {
        this.create("searches", table => {
            table.increments()
            table.string("latitude")
            table.string("longitude")
            table.text("response")
            table.timestamps()
        })
    }

    down() {
        this.drop("searches")
    }
}

module.exports = SearchSchema

这来自proxy/database/migrations/x_search_schema.js

我们添加了latitudelongituderesponse字段。 尽管前两个包含浮点数据,但它们还是string有意义,因为我们想使用它们进行子字符串搜索。

接下来,让我们创建一个API端点:

"use strict"

const Route = use("Route")

// we don't need this anymore...
// Route.on("/").render("welcome")

Route.get("search", ({ request, response }) => {
    const { latitude, longitude } = request.all()

    // ...do something with latitude and longitude
})

这是来自proxy/start/routes.js

每个AdonisJS路由均在routes.js文件中定义。 在这里,我们注释掉了最初的“欢迎”路线,并添加了新的“搜索”路线。 该闭包由上下文对象调用; 可以访问requestrequest对象。

我们可以期望搜索请求提供latitudelongitude查询字符串参数; 我们可以通过request.all获得这些。 我们应该检查是否有任何模糊相关的坐标。 我们可以使用Search模型来做到这一点:

const Search = use("App/Models/Search")

const searchablePoint = (raw, characters = 8) => {
    const abs = Math.abs(parseFloat(raw))
    return parseFloat(abs.toString().substr(0, characters))
}

Route.get("search", async ({ request, response }) => {
    const { latitude, longitude } = request.all()

    const searchableLatitude = searchablePoint(latitude)
    const searchableLongitude = searchablePoint(longitude)

    // console.log(searchableLatitude, searchableLongitude)

    const searches = await Search.query()
        .where("latitude", "like", `%${searchableLatitude}%`)
        .where("longitude", "like", `%${searchableLongitude}%`)
        .fetch()

    // console.log(searches.toJSON())

    response.send("done")

    // ...do something with latitude and longitude
})

这是来自proxy/start/routes.js

我们首先导入Search模型。 这是我们创建的数据库表(使用迁移)的代码表示。 我们将使用它来查询数据库中的“附近”搜索。

在此之前,我们需要一种搜索​​近似坐标的方法。 searchablePoint函数采用原始坐标字符串并创建一个绝对浮点值,并从字符串开头删除了可选的- 。 然后,它返回坐标字符串的前8字符。 这会将-33.872527399999996缩短为33.872527 。 然后,我们可以在SQL“ where like”子句中使用这8个字符,以返回所有具有相似坐标字符串的搜索。

AdonisJS使用asyncawait关键字效果很好。 诸如Search.query方法Search.query返回Search.query ,因此我们可以await其结果的同时仍然编写100%异步代码。

我跳过了很多AdonisJS详细信息,我真的不喜欢这样做。 如果您正在为这一部分而苦苦挣扎; 在Twitter上与我交谈 ,我会为您指明正确的方向。

匹配附近的位置

现在我们有了“附近”的位置,我们可以将它们的相对距离与用户站立的位置进行比较。 如果您还没有Google API密钥,请返回上一教程,了解如何获得它。 我们即将成为Google Distance Matrix服务:

https://maps.googleapis.com/maps/api/distancematrix/json? ↵
    mode=walking& ↵
    units=metric& ↵
    origins=-33.872527399999996,18.6339164& ↵
    destinations=-33.872527399999997,18.6339165& ↵
    key=YOUR_API_KEY

Distance Matrix服务实际上允许多个原点,因此我们可以将您之前的所有搜索合并为冗长的原点字符串:

const reduceSearches = (acc, search) => {
    const { latitude, longitude } = search
    return `${acc}|${latitude},${longitude}`
}

Route.get("search", async ({ request, response }) => {
    const { latitude, longitude } = request.all()

    // ...get searches

    const origins = searches
        .toJSON()
        .reduce(reduceSearches, "")
        .substr(1)

    // console.log(origins)

    response.send("done")

    // ...do something with latitude and longitude
})

这是来自proxy/start/routes.js

我们可以将搜索结果转换为对象数组。 这很有用,因为我们可以缩小数组,将每次搜索的纬度和经度组合成一个字符串。 该字符串将以|开头| ,所以我们需要从索引1开始的字符串。

我是浏览器fetch API的狂热者,所以让我们安装一个NodeJS polyfill:

npm install --save node-fetch-polyfill

使用此polyfill,我们可以获取Google的距离列表:

"use strict"

const fetch = use("node-fetch-polyfill")

const Env = use("Env")
const Route = use("Route")
const Search = use("App/Models/Search")

const searchablePoint = (raw, characters = 8) => {
    // ...
}

const reduceSearches = (acc, search) => {
    // ...
}

Route.get("search", async ({ request, response }) => {
    const { latitude, longitude } = request.all()

    // ...get origins

    const key = Env.get("GOOGLE_KEY")

    const distanceResponse = await fetch(
        `https://maps.googleapis.com/maps/api/distancematrix/json? ↵
            mode=walking&units=metric&origins=${origins}& ↵
            destinations=${latitude},${longitude}&key=${key}`,
    )

    const distanceData = await distanceResponse.json()

    // console.log(distanceData)

    response.send("done")

    // ...do something with data
})

这是来自proxy/start/routes.js

fetch返回一个承诺,因此我们可以await它。 响应有一个json方法,它将原始响应序列化为JSON数组或对象,然后结合原点坐标(所有与起点相似的东西),得到所有距离的列表。 响应对象与原点坐标的顺序相同。 随着我们的继续,这将变得有用……

AdonisJS提供了自己的.env文件支持。 我们可以放弃上一教程的env.example.jsenv.js文件; 并仅使用现有的.env.env.example 。 我已经将GOOGLE_KEY都添加到了两者中,你也应该添加了。 然后,我们可以使用Env.get获取值。

我们可以检查结果,以查找是否在要求的坐标的10米范围内:

Route.get("search", async ({ request, response }) => {
    const { latitude, longitude } = request.all()

    // ...get distance data

    for (let i in distanceData.rows) {
        const { elements } = distanceData.rows[i]

        if (typeof elements[0] === "undefined") {
            continue
        }

        if (elements[0].status !== "OK") {
            continue
        }

        const matches = elements[0].distance.text.match(/([0-9]+)\s+m/)

        if (matches === null || parseInt(matches[1], 10) > 10) {
            continue
        }

        response.json(JSON.parse(searchRows[i].response))
        return
    }

    // ...cached result not found, fetch new data!
})

这是来自proxy/start/routes.js

我们可以遍历距离行,对每行进行一些检查。 如果原点坐标无效,则距离矩阵服务可能会为该行返回错误。 如果元素格式不正确(未定义或错误),则跳过该行。

如果存在有效的度量(以nm的形式表示,其中n为1 – 10); 然后我们返回该行的响应。 我们不需要新的庇护所数据。 在可能的情况下,我们没有缓存附近的坐标; 我们可以请求新数据:

Route.get("search", async ({ request, response }) => {
    const { latitude, longitude } = request.all()

    // ...check for cached data

    const refugeResponse = await fetch(
        `https://www.refugerestrooms.org/api/v1/restrooms/by_location.json? ↵
            lat=${latitude}&lng=${longitude}`,
    )

    const refugeData = await refugeResponse.json()

    await Search.create({
        latitude,
        longitude,
        response: JSON.stringify(refugeData),
    })

    response.json(refugeData)
    return
})

这是来自proxy/start/routes.js

如果没有缓存的搜索,我们将请求一组新的避难结果。 我们可以原封不动地退还它们。 但在将搜索保存到数据库之前不可以。 第一个请求应比后续请求稍慢。 我们实质上是将避难所API处理工作转移到Distance Matrix API上。 现在,我们还有一种方法来管理CORS权限。

在浏览器中获取结果

让我们开始在浏览器中使用这些数据。 尝试建立一个ParcelJS构建链(或者回头看一下我们之前做的教程)。 这包括将WRLD SDK安装和加载到app.js文件中。 它看起来应该像这样:

const Wrld = require("wrld.js")

const tester = async () => {
    const response = await fetch(
        "http://127.0.0.1:3333/search? ↵
            latitude=-33.872527399999996&longitude=18.6339164",
    )

    const data = await response.json()

    console.log(data)
}

tester()

这是来自app/app.js

您应该可以将其与以下命令捆绑在一起:

parcel index.html

您的文件夹结构应类似于以下内容:

包裹文件夹结构

它与上一教程中制作的文件夹结构相同。 您也可以复制所有内容,用上面看到的内容替换app.js的内容。 tester功能旨在证明我们尚无法从缓存代理服务器请求数据。 为此,我们需要启用AdonisJS CORS层

"use strict"

module.exports = {
    /*
    |--------------------------------------------------------------------------
    | Origin
    |--------------------------------------------------------------------------
    |
    | Set a list of origins to be allowed...
    */
    origin: true,

    // ...rest of the CORS settings
}

这是来自proxy/config/cors.js

如果将origin设置为true ,则所有CORS请求都将成功。 在生产环境中,您可能希望提供一个有条件地返回true的闭包。 这样您就可以限制谁可以对此API发出请求。

如果刷新浏览器,则ParcelJS所服务的URL是打开的。 您现在应该可以在控制台中看到结果:

核心工作

请勿注意该警告。 只是ParcelJS Hot Module Replacement有一点时间...

从现在开始,我们可以开始使用缓存代理服务器来查找与一组坐标最接近的设施。 让我们添加地图!

与WRLD集成

首先,将第一个教程中的env.jsenv.example.js文件添加到app文件夹中。 然后,我们可以使用它们再次渲染地图:

const Wrld = require("wrld.js")
const env = require("./env")

const keys = {
    wrld: env.WRLD_KEY,
}

// ...tester code

window.addEventListener("load", async () => {
    const map = Wrld.map("map", keys.wrld, {
        center: [40.7484405, -73.98566439999999],
        zoom: 15,
    })
})

这是来自app/app.js

我们回到帝国大厦。 不过,如果我们可以从更靠近用户的地方开始会更好。 并且,如果我们可以提供一种使用自定义坐标覆盖地理位置的方法。 让我们利用HTML5 Geolocation API:

window.addEventListener("load", async () => {
    let map

    navigator.geolocation.getCurrentPosition(
        position => {
            const { latitude, longitude } = position.coords

            map = Wrld.map("map", keys.wrld, {
                center: [latitude, longitude],
                zoom: 15,
            })
        },
        error => {
            map = Wrld.map("map", keys.wrld, {
                center: [40.7484405, -73.98566439999999],
                zoom: 15,
            })
        },
    )
})

这是来自app/app.js

我们可以使用getCurrentPosition来获取用户的最佳猜测坐标。 如果用户拒绝对地理位置数据的请求,或者发生其他问题,我们可以默认使用一组已知坐标。

没有记录的错误参数,但是我喜欢将参数放在此处以使代码更清晰。

这就是自动位置检测。 现在,如果我们想用自定义坐标覆盖它呢? 我们可以向HTML添加一些表单输入,并使用一些Javascript作为目标输入:

<body>
    <div id="map"></div>
    <div class="controls">
        <input type="text" name="latitude" />
        <input type="text" name="longitude" />
        <input type="button" name="apply" value="apply" />
    </div>
    <script src="./app.js"></script>
</body>

这是来自app/index.html

.controls {
    position: absolute;
    top: 0;
    right: 0;
    background: rgba(255, 255, 255, 0.5);
    padding: 10px;
}

这是来自app/app.css

window.addEventListener("load", async () => {
    let map

    const latitudeInput = document.querySelector("[name='latitude']")
    const longitudeInput = document.querySelector("[name='longitude']")
    const applyButton = document.querySelector("[name='apply']")

    applyButton.addEventListener("click", () => {
        map.setView([latitudeInput.value, longitudeInput.value])
    })

    navigator.geolocation.getCurrentPosition(
        position => {
            const { latitude, longitude } = position.coords

            latitudeInput.value = latitude
            longitudeInput.value = longitude

            map = Wrld.map("map", keys.wrld, {
                center: [latitude, longitude],
                zoom: 15,
            })
        },
        error => {
            map = Wrld.map("map", keys.wrld, {
                center: [40.7484405, -73.98566439999999],
                zoom: 15,
            })
        },
    )
})

这是来自app/app.js

我们首先获得对添加的新input元素的引用。 单击applyButton ,我们想更新地图。 地理位置数据成功后,我们可以使用适当的纬度和经度填充这些输入。

现在,如何突出显示附近的设施建筑物?

let map
let highlightedFacilities = []

const highlightFacilities = async (latitude, longitude) => {
    for (let facility of highlightedFacilities) {
        facility.remove()
    }

    highlightedFacilities = []

    const facilitiesResponse = await fetch(
        `http://127.0.0.1:3333/search?latitude=${latitude}&longitude=${longitude}`,
    )

    const facilitiesData = await facilitiesResponse.json()

    for (let facility of facilitiesData) {
        // console.log(facility)

        const color =
            facility.upvote >= facility.downvote
                ? [125, 255, 125, 200]
                : [255, 125, 125, 200]

        const highlight = Wrld.buildings
            .buildingHighlight(
                Wrld.buildings
                    .buildingHighlightOptions()
                    .highlightBuildingAtLocation([
                        facility.latitude,
                        facility.longitude,
                    ])
                    .color(color),
            )
            .addTo(map)

        highlightedFacilities.push(highlight)
    }
}

window.addEventListener("load", async () => {
    // ...add button event

    navigator.geolocation.getCurrentPosition(
        position => {
            const { latitude, longitude } = position.coords

            // ...create map

            map.on("initialstreamingcomplete", () => {
                highlightFacilities(latitude, longitude)
            })
        },
        error => {
            // ...create map

            map.on("initialstreamingcomplete", () => {
                highlightFacilities(40.7484405, -73.98566439999999)
            })
        },
    )
})

这是来自app/app.js

创建地图或更改地图焦点时,可以调用highlightFacilities函数。 这接受latitudelongitude ,删除所有以前突出显示的建筑物,并突出显示由缓存代理搜索返回的所有建筑物。

对于具有50%或更高投票率的建筑物,我们选择绿色突出显示; 其余部分用红色突出显示。 这将使寻找更好的设施变得更加容易。

我们甚至可以使用地图的当前中心来更新覆盖输入,以便用户可以平移并找到靠近该地图区域的新浴室。 我们还可以使突出显示的建筑物更加清晰。 通过添加地图标记并在按下/单击时显示弹出窗口:

let map
let highlightedFacilities = []
let highlighterMarkers = []

const highlightFacilities = async (latitude, longitude) => {
    for (let facility of highlightedFacilities) {
        facility.remove()
    }

    highlightedFacilities = []

    for (let marker of highlighterMarkers) {
        marker.remove()
    }

    highlighterMarkers = []

    const facilitiesResponse = await fetch(
        `http://127.0.0.1:3333/search?latitude=${latitude}&longitude=${longitude}`,
    )

    const facilitiesData = await facilitiesResponse.json()

    for (let facility of facilitiesData) {
        const location = [facility.latitude, facility.longitude]

        // ...add highlight color

        const intersection = map.buildings.findBuildingAtLatLng(location)

        let marker

        if (intersection.found) {
            marker = L.marker(location, {
                elevation: intersection.point.alt,
                title: facility.name,
            }).addTo(map)
        } else {
            marker = L.marker(location, {
                title: facility.name,
            }).addTo(map)
        }

        if (facility.comment) {
            marker.bindPopup(facility.comment).openPopup()
        }

        highlighterMarkers.push(marker)
    }
}

window.addEventListener("load", async () => {
    // ...add button event

    navigator.geolocation.getCurrentPosition(
        position => {
            const { latitude, longitude } = position.coords

            // ...create map

            map.on("panend", event => {
                const { lat, lng } = map.getBounds().getCenter()

                latitudeInput.value = lat
                longitudeInput.value = lng
            })
        },
        error => {
            // ...create map

            map.on("panend", event => {
                const { lat, lng } = map.getBounds().getCenter()

                latitudeInput.value = lat
                longitudeInput.value = lng
            })
        },
    )
})

这是来自app/app.js

我们可以将panend事件添加到我们创建地图的地方。 当用户开始平移并且地图静止时,将触发此操作。 我们得到可见的地图边界,并从中得到中心。

然后,在highlightFacilities函数中,我们添加了标记和可选的弹出窗口(如果有要显示的建议。这使得发现突出显示的建筑物以及查找有关其所包含设施的任何其他信息更加容易。

增加气氛

最后,向地图视图添加一些大气效果。 首先,我们可以将“天气状况”端点添加到我们的缓存代理中:

Route.get("condition", async ({ request, response }) => {
    const { latitude, longitude } = request.all()

    const key = Env.get("OPENWEATHER_KEY")

    const weatherResponse = await fetch(
        `http://api.openweathermap.org/data/2.5/weather? ↵
            lat=${latitude}&lon=${longitude}&appid=${key}`,
    )

    const weatherData = await weatherResponse.json()

    response.json(weatherData)
})

这是来自proxy/start/routes.js

这需要创建一个开放式天气地图帐户。 我们到达那里的API密钥需要添加到.env.env.example 。 然后,我们可以开始在浏览器中请求此数据。 如果该地区的天气与WRLD天气预设之一匹配; 我们可以将其应用于地图。 我们还可以使用浏览器的时间来设置一天中的时间:

const Wrld = require("wrld.js")
const env = require("./env")

const keys = {
    wrld: env.WRLD_KEY,
}

let map
let highlightedFacilities = []
let highlighterMarkers = []

const highlightFacilities = async (latitude, longitude) => {
    // ...highlight buildings and add markers

    try {
        const weatherResponse = await fetch(
            `http://127.0.0.1:3333/condition? ↵
                latitude=${latitude}&longitude=${longitude}`,
        )

        const weatherData = await weatherResponse.json()

        if (weatherData.weather && weatherData.weather.length > 0) {
            const condition = weatherData.weather[0].main.toLowerCase()

            switch (condition) {
                case "snow":
                    map.themes.setWeather(Wrld.themes.weather.Snowy)
                    break
                case "few clouds":
                case "scattered clouds":
                case "broken clouds":
                    map.themes.setWeather(Wrld.themes.weather.Overcast)
                    break
                case "mist":
                    map.themes.setWeather(Wrld.themes.weather.Foggy)
                    break
                case "shower rain":
                case "rain":
                case "thunderstorm":
                    map.themes.setWeather(Wrld.themes.weather.Rainy)
                    break
                default:
                    map.themes.setWeather(Wrld.themes.weather.Clear)
                    break
            }
        }

        const time = new Date().getHours()

        if (time > 5 && time <= 10) {
            map.themes.setTime(Wrld.themes.time.Dawn)
        } else if (time > 10 && time <= 16) {
            map.themes.setTime(Wrld.themes.time.Day)
        } else if (time > 16 && time < 21) {
            map.themes.setTime(Wrld.themes.time.Dusk)
        } else {
            map.themes.setTime(Wrld.themes.time.Night)
        }
    } catch (e) {
        // weather and time effects are entirely optional
        // if they break, for whatever reason, they shouldn't kill the app
    }
}

const latitudeInput = document.querySelector("[name='latitude']")
const longitudeInput = document.querySelector("[name='longitude']")
const applyButton = document.querySelector("[name='apply']")

const initMapEvents = async (latitude, longitude) => {
    map.on("initialstreamingcomplete", () => {
        highlightFacilities(latitude, longitude)
    })

    map.on("panend", event => {
        const { lat, lng } = map.getBounds().getCenter()

        latitudeInput.value = lat
        longitudeInput.value = lng
    })

    applyButton.addEventListener("click", () => {
        map.setView([latitudeInput.value, longitudeInput.value])
        highlightFacilities(latitudeInput.value, longitudeInput.value)
    })
}

window.addEventListener("load", async () => {
    navigator.geolocation.getCurrentPosition(
        position => {
            // ...create map

            initMapEvents(latitude, longitude)
        },
        error => {
            // ...create map

            initMapEvents(latitude, longitude)
        },
    )
})

这是来自app/app.js

我借此机会将所有创建地图后的代码移到可重用的initMapEvents函数中。 另外,我已经将天气和时间效果添加到了highlightBuildings函数中。 因为这是更改这些内容的最合理的地方。 如果用户输入沙漠坐标,我们不希望地图继续下雪……

不幸的是,如果没有更多的工作,一天中的时间总是相对于用户的浏览器而言的,但是我认为在本教程中这样做不是必需的。

摘要

这是一个有趣的项目。 不仅如此,这是您可以制作并发展为业务的东西(希望比乔治的各种功绩取得更大的成功)。 也许您发现了人们需要应用程序寻找的另一种东西。 如果您具有正确的权限和帐户限制(例如OpenWeatherMap,Google,Refuge和WRLD),则可以创建任何种类的finder应用。

从我的角度来看,有两种方法可从此类应用中获利。 您可以在iOS和Android商店中出售它。 您可以将其构建到React Native应用程序中,甚至只是一个简单的Web应用程序包装器。

或者,您可以在屏幕上显示广告。 用户可以付费删除这些广告,但是您可能还需要考虑一些有关帐户登录和/或恢复购买的信息。

无论哪种方式,您都可以构建它。 少于200行代码。 更进一步,为每个兴趣点添加方向。 也许甚至允许用户过滤兴趣点,以便仅显示关闭3。

WRLD提供了您需要的大多数工具。

From: https://www.sitepoint.com/build-seinfeld-bathroom-finder/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值