Web 应用程序极大地受益于地图的包含,为用户提供了有价值的基于位置的信息。地图改变了我们与世界的互动,从导航陌生的地方到发现附近的餐馆。因此,近年来,将地图集成到网站中变得越来越流行。然而,设计既实用又用户友好的地图可能会带来挑战,特别是对于那些缺乏该领域经验的人来说。在本文中,我将分享有关如何在浏览器中创建有效地图的有用技巧。
我们来讨论一下技术。使用地图时,我们通常使用三层:
呈现用户界面,其中包括按钮和表单。在我们的堆栈中,React 扮演着这个角色;
渲染地图并启用用户交互。为此,我们使用 Mapbox;
从服务器获取数据,例如有关标记或多边形的信息。为了检索数据,我们使用浏览器的内置获取功能。
让我们回顾一下每个项目,以便更好地了解我们在使用地图时的技术堆栈。
这
由于更改页面上的元素对于浏览器来说是最昂贵的操作,因此必须尽可能高效地执行此操作。为了解决这个问题,Facebook 工程师开发了 React 库,它可以快速、直接地更改页面上的元素。除了在页面上提供快速状态更改之外,React 还允许我们以声明方式执行此操作,而无需直接使用 DOM 元素。相反,我们通常使用抽象
// It's our state. Is the user our friend or not? // false by default const [isFriend, setIsFriend] = useState(false) // Depending on the state, we show the text on the button const buttonText = isFriend ? 'Your my Friend' : 'Add as Friend' // There is JSX, syntax for UI // In this case, we display a button, when clicked, we change the state return ( <button onClick={() => setIsFriend(true)}>{buttonText}</button> )
可以将组件与普通 DOM 元素(例如表单、按钮和输入)嵌套在层次结构的底部。通过组装这些简单的元素,我们可以创建更复杂的元素,例如完整的表单:
const Form = () => ( <form> <input name="Email"/> <input name="Password"/> </form> ) const App = () => ( <main> <h1>My form!</h1> <Form /> </main> )
React 如何在地图上下文中帮助我们?由于页面上的地图是交互式的,类似于按钮或表单,我们的目标是通过点击地图等事件来优化其渲染和交互。 React 可以帮助实现这种优化。以下是其工作原理的示例:
// Use React to render the map with different event handlers // and render markers return ( <BaseMap onInitMap={() => console.log('I am alive!')} onClickMap={() => console.log('Click!')} onDestroyMap={() => console.log('Oh no!')} > <ClustersMarkers /> <PostsMarkers /> <ListingsMarkers /> </BaseMap> )
使用 React 时,必须记住,它允许高效地操作页面上的元素、快速更改以及通过事件与它们交互。这是通过类似于 HTML 的抽象来实现的,从而可以轻松地从简单的组件创建复杂的组件。
现在,让我们讨论地图本身。创建和使用地图可能具有挑战性,只有少数产品公司可以从头开始设计地图。通常,大多数人依赖带有经过尝试和测试的用户友好 API 的预制库。
有许多动态地图提供商可供使用,包括 Google 地图、Leaflet、Bing 地图、Mapbox 等。不过,我们将重点关注
Mapbox 优惠
此外,Mapbox 还提供
Mapbox 提供了多种
让我们回顾一下 Mapbox 地图。什么是
它在页面上的 HTML 元素中初始化地图;
它加载并渲染构成地图的图像;
它使用 GeoJson 作为输入数据绘制附加元素,例如标记;
它生成可以处理的事件,例如单击或缩放更改。
让我们仔细看看其中的每一项。
Mapbox 专注于使用图块进行地图渲染。图块是构成较大地图的小方形图像。图块的默认大小为 512x512 像素,可以是
如您所知,Mapbox Studio 允许我们选择要包含在地图图块中的特定数据。然后将这些瓷砖放置在
<canvas width="100" height="100" />
Mapbox 处理图块的加载、插入和更新。我们所要做的就是指定希望地图显示的位置以及初始条件,例如缩放级别或地图坐标。要使用 Mapbox,您需要
mapboxgl.accessToken = 'YOUR_MAPBOX_ACCESS_TOKEN'; const map = new mapboxgl.Map({ container: 'map', // we can use an Id or an element style: 'mapbox://styles/mapbox/streets-v11', // URL for styles center: [-74.5, 40], // initial coordinates [lng, lat] zoom: 9, // initial zoom });
之后,我们将在页面上的 id 为“map”的元素中获取地图。
为了在地图上向用户提供更多信息,我们经常显示某个机构的位置或特定区域的边界。为了实现这一点,我们使用一种特定的数据格式,称为
GeoJSON 是在地图上存储地理结构的标准格式。它可以存储描述地理对象的各种原始类型,例如地址、位置、街道、高速公路、边界、国家、州以及这些的组合,称为多部分。 GeoJSON 于 2008 年推出,表示方式如下:
{ "type": "Feature", // also can be FeatureCollection, it's collection of Feature "geometry": { "type": "Point", // also can be LineString, Polygon, MultiPolygon "coordinates": [125.6, 10.1] // for other types you can use Array with coordinates }, "properties": { // it's metadata, we can you that to show something on the map "name": "Dinagat Islands" } }
我们来谈谈Mapbox中使用的坐标系。默认情况下,Mapbox 使用map.setProjection
方法。
现在,我们将讨论如何在地图上显示 GeoJSON。 Mapbox 提供了两个对我们很有帮助的实体:
要在地图上显示多边形或标记,我们必须从服务器检索 GeoJson 格式的数据。然后,我们创建一个源,将数据输入其中,并将其连接到所需的层。
const geoJsonFeature = { 'type': 'Feature', 'geometry': { 'type': 'Polygon', 'coordinates': [ [-67.13734, 45.13745], [-66.96466, 44.8097], [-68.03252, 44.3252], [-67.13734, 45.13745] ] } } // Create source with our data map.addSource('ourSource', { 'type': 'geojson', 'data': geoJsonFeature }); // Add layer for background map.addLayer({ 'id': 'background', 'type': 'fill', 'source': 'ourSource', // название нашего source 'layout': {}, 'paint': { 'fill-color': '#0080ff', 'fill-opacity': 0.5 } }); // Add layer for border map.addLayer({ 'id': 'border', 'type': 'line', 'source': 'ourSource', 'layout': {}, 'paint': { 'line-color': '#000', 'line-width': 3 } });
运行这段代码后,我们得到结果:
要了解有关此主题的更多信息,您可以参考on
方法,类似于我们使用 DOM 元素的方式。
map.on('mousemove', (e) => { console.log(JSON.stringify(e.point)); }); // Result: {"x":330,"y":49}
总而言之,我们需要记住什么? Mapbox 允许我们显示地图、在其上绘制数据以及处理地图事件。同时,Mapbox 负责加载和显示图像(图块)。
一句话关于
要记住什么?我们从服务器检索数据,有很多库用于此目的,但我们将使用 fetch。接下来,我们将了解在使用地图时如何具体执行此操作,因为存在细微差别。
现在让我们看看上述技术如何协同工作。首先,我们将使用 fetch 检索用于显示多边形的数据。然后我们将声明地图的初始化,并在加载后将多边形添加到地图中。
您还可以在以下位置找到一个工作示例
const useFetch = () => { /* Our data { 'type': 'Feature', 'geometry': { 'type': 'Polygon', 'coordinates': [ [ [-67.13734, 45.13745], [-68.03252, 44.3252], [-68.90478, 47.18479], [-67.13734, 45.13745], ] ] } } */ const [data, setData] = useState(null) useEffect(() => { fetch('https://our-api.com/polygon') .then(response => response.json()) .then(setData) .catch(e => { console.error(e) }) }, [setData]) return { data } } const BaseMap = () => { // Use the hook to fetch data const { data } = useFetch(GET_REGION); // Map instance const map = useRef(null); // DOM element const mapContainer = useRef(null); // Main logic - init the map and add the event useEffect(() => { if (map.current) { return; // initialize map only once } mapboxgl.accessToken = 'YOUR_MAPBOX_ACCESS_TOKEN'; map.current = new mapboxgl.Map({ container: mapContainer.current, style: 'mapbox://styles/mapbox/light-v10', // style URL (it's Mapbox's core style) center: [-68.137343, 45.137451], // starting position zoom: 5 // starting zoom }); // Handle event map.on('load', () => { const sourceId = 'source-region' // Add a data source containing GeoJSON data map.addSource(sourceId, { 'type': 'geojson', 'data': data.region // our data from Apollo }); // Add a new layer to visualize the polygon map.addLayer({ 'id': 'background', 'type': 'fill', 'source': sourceId, // reference the data source 'paint': { 'fill-color': '#0080ff', // blue color fill 'fill-opacity': 0.5 } }); // Add a black outline around the polygon map.addLayer({ 'id': 'outline', 'type': 'line', 'source': sourceId, 'paint': { 'line-color': '#000', 'line-width': 3 } }); }); }); return <div ref={mapContainer} />; }
我们研究了支撑我们未来架构的技术堆栈。在下面的文章中,我们将讨论帮助设计地图架构的原则,如何实现模块的最大低耦合和高内聚,以及如何维护和开发可扩展的地图系统。
非常感谢您的关注!祝你有美好的一天。