时间关系,先写个简要文档,之后会补一个全面的文档。
- 集成 高德 和 谷歌 两个地图
- 支持 vue 2.7 和 vue 3
- 统一的接口,不需要写两套
# 安装依赖包
npm install @heycar/heycars-map --save
import "@heycar/heycars-map/dist/style.css";
目前地图提供了 3 个字体 css variables 用于外部制定特殊字体
/* 使用特殊字体 HarmonyOS_Sans_SC_Regular */
body {
--HEYCAR_MAP_CSS_VAR_FONT_REGULAR: HarmonyOS_Sans_SC_Regular, "PingFangSC-Regular", "PingFang SC";
--HEYCAR_MAP_CSS_VAR_FONT_MEDIUM: HarmonyOS_Sans_SC_Medium, "PingFangSC-Medium", "PingFang SC";
--HEYCAR_MAP_CSS_VAR_FONT_SEMI_BOLD: HarmonyOS_Sans_SC_Bold, "PingFangSC-Semibold", "PingFang SC";
}
常用数据结构
export type CoordinateType = "wgs84" | "gcj02" | "bd09";
export type CoordinatePoint = {
lng: number;
lat: number;
type: CoordinateType;
};
export type CoordinatePlace = {
lng: number;
lat: number;
type: CoordinateType;
name: string;
displayName: string;
};
export interface CoordinateZone {
name: string;
path: CoordinatePoint[];
}
export interface CoordinateRecommendZonePlaces {
// 是否强制吸附
adsorption?: boolean;
type?: RecommendType;
// 是否可以提供服务
available?: boolean;
zone?: CoordinateZone;
places?: CoordinatePlace[];
}
export interface CoordinateValueOfOnChangeRecommendPlace {
type: RecommendType;
isSameZone: boolean;
place: CoordinatePlace;
inputPlace: CoordinatePlace;
zone?: CoordinateZone;
}
export interface CoordinateGeoPosition {
position: CoordinatePoint;
coords?: GeolocationCoordinates;
}
export interface CoordinateTrackPoint {
lng: number;
lat: number;
type: CoordinateType;
angle?: number;
speed?: number;
timestamp: number;
}
export enum CenterPlaceStatus {
GEO_LOADING = "GEO_LOADING",
QUERYING_INFO = "QUERYING_INFO",
SERVICE_UNAVAILABLE = "SERVICE_UNAVAILABLE",
OK = "OK",
}
export type GoogleConnectionStatus = "pending" | "connected" | "unconnected";
// GeolocationPositionError 类型参考 [MDN Reference](https://developer.mozilla.org/docs/Web/API/GeolocationPositionError)
export interface BusinessGeolocationPositionError extends GeolocationPositionError {
// isBusinessTimeout 是产品定义的 业务超时,不同于标准 GeolocationPositionError 的TIMEOUT
// 参考 http://p.heycars.cn/zentaopms/www/index.php?m=story&f=view&storyID=1212
// 参考 https://doc.weixin.qq.com/doc/w3_AZIAowasAIcGieCNESnQZe1rhTyaK?scode=ACcAmge7AAou1TTqYiAZIAowasAIc
isBusinessTimeout: boolean;
}
// Restricted(绿区)Forbidden(红区)Recommend(推荐点)
export type RecommendType = "Restricted" | "Forbidden" | "Recommend";
- 对于 jsx/tsx 文件的例子
<MapProvider
// 高德地图 api key
amapKey={amapApiKey}
// 高德地图 secret
amapSecret={amapApiSecret}
// 谷歌地图 id
gmapId={gmapId}
// 谷歌地图 api key
gmapKey={gmapApiKey}
// 使用哪个地图供应商,目前两个供应商: amap 高德 / gmap 谷歌
supplier={"amap"}
// 地图加载失败时显示的标题
renderLoadFailedTitle={() => (supplier === "gmap" ? "未能成功访问谷歌地图" : "高德地图加载失败")}
// 地图加载失败时显示的描述
renderLoadFailedDescription={() =>
supplier === "gmap" ? "请确认您的网络能正常访问谷歌地图, \n或切回高德地图" : undefined
}
>
...
</MapProvider>
- 对于 vue 文件的例子
<MapProvider
:gmap-id="gmapId"
:gmap-key="gmapApiKey"
:amap-key="amapApiKey"
:amap-secret="amapApiSecret"
supplier="amap"
render-load-failed-title='() => supplier === "gmap" ? "未能成功访问谷歌地图" : "高德地图加载失败"'
render-load-failed-description='() => supplier === "gmap" ? "请确认您的网络能正常访问谷歌地图,\n或切回高德地图" : undefined'
>
...
</MapProvider>
为了方便集成,已经将常用业务逻辑集成在四个业务组件里面,下面是推荐使用的业务组件
BusinessRecomendPlaceMap
BusinessReselectPlaceMap
BusinessQuotingMap
BusinessTaxiServiceMap
BusinessTaxiEndMap
下面三个是推荐搭配使用的业务 hooks
useBusinessRecomendPlaceMap
useBusinessReselectPlaceMap
useBusinessQuotingMap
useBusinessTaxiServiceMap
对于 jsx/tsx 文件的例子
import { defineComponent } from "vue";
import { BusinessRecomendPlaceMap, useBusinessRecomendPlaceMap } from "@heycar/heycars-map";
export default defineComponent({
setup() {
const {
centerPlace,
mapContext,
setCenterPlaceByUserSpecified,
setCenterPlaceByUserSpecifiedInZone,
} = useBusinessRecomendPlaceMap();
// 演示 setCenterPlaceByUserSpecifiedInZone 的用法
const demoForUsage = () => {
setCenterPlaceByUserSpecifiedInZone({
// 期望的地图中心点
place: {
lng: 139.56,
lat: 35.56,
type: "gcj02",
name: "user specified place in zone",
displayName: "user specified place in zone",
},
// 推荐点和绿区信息
recommends: {
// 绿区
zone: {
name: "zone 1",
path: [
{ lng: 139.569, lat: 35.555, type: "gcj02" },
{ lng: 139.558, lat: 35.55, type: "gcj02" },
{ lng: 139.56, lat: 35.565, type: "gcj02" },
],
},
// 绿区内的推荐点列表
places: [
{
lng: 139.56,
lat: 35.56,
type: "gcj02",
name: "user specified place in zone",
displayName: "user specified place in zone",
},
{
lng: 139.562,
lat: 35.562,
type: "gcj02",
name: "recommend place 2",
displayName: "recommend place 2",
},
],
},
});
};
return () => (
<BusinessRecomendPlaceMap
class={"demo"}
geoLoadingTitle={"正在获取您当前的位置"}
unavailableTitle={"当前区域暂未开通服务"}
forbiddenTitle={"当前区域不可叫车"}
emptyTitle={"当前位置"}
queryingTitle={"正在获取地址信息"}
recomendDescription={"您将在此处上车"}
geoErrorOnceNotificationKey="BusinessReselectPlaceMap_GeoErrorOnceKey"
defaultCenterPlace={(place) =>
place ?? {
lng: 139.777777,
lat: 35.777777,
type: "gcj02",
name: "default place name",
displayName: "default place displayName",
}
}
getAvailable={() => Promise.resolve(true)}
getRecomendPlace={getRecomendPlace}
getDefaultCenterPlace={getDefaultCenterPlace}
renderPlacePhoto={(place) => {
place;
return "https://oss-now.heycars.cn/image/graphicGuidance/file/hmlh38_xs6_DdksNX0_TbgF0lKXp.jpg";
}}
renderPlaceTag={(place) => {
place;
return "最近使用";
}}
mapContext={mapContext}
onChangePlace={(place) => {
console.log("地图中心点变化时触发 place = ", place);
}}
onChangeRecomandPlace={({ place, inputPlace, type, zone }) => {
console.log("用户操作地图,计算推荐点后得出的最终位置时触发,此时可以向后端查询城市信息");
console.log(
"计算推荐点之前的地址是: ",
inputPlace,
" 最终的地址是: ",
place,
" 推荐类型: ",
type,
"绿区(或红区):",
zone,
);
}}
onClickLocatorText={() =>
console.log("用户点击了蓝色光标文字触发,此时可以执行用户点击起点输入框相同的逻辑")
}
onClickLocatorPhoto={() =>
console.log("用户点击了蓝色光标图片触发,此时可以执行用户点击起点输入框相同的逻辑")
}
onGeoError={({ isBusinessTimeout }) => {
console.log(
"获取GPS失败时触发,此时可以弹框告诉用户",
isBusinessTimeout ? "给用户超时提示" : "给用户无权限反馈",
);
}}
/>
);
},
});
对于 jsx/tsx 文件的例子
import { defineComponent } from "vue";
import { BusinessReselectPlaceMap, useBusinessReselectPlaceMap } from "@heycar/heycars-map";
export default defineComponent({
setup() {
const {
centerPlace,
mapContext,
setCenterPlaceByUserSpecified,
setCenterPlaceByUserSpecifiedInZone,
} = useBusinessReselectPlaceMap();
// 演示 setCenterPlaceByUserSpecifiedInZone 的用法
const demoForUsage = () => {
setCenterPlaceByUserSpecifiedInZone({
// 期望的地图中心点
place: {
lng: 139.56,
lat: 35.56,
type: "gcj02",
name: "user specified place in zone",
displayName: "user specified place in zone",
},
// 推荐点和绿区信息
recommends: {
// 绿区
zone: {
name: "zone 1",
path: [
{ lng: 139.569, lat: 35.555, type: "gcj02" },
{ lng: 139.558, lat: 35.55, type: "gcj02" },
{ lng: 139.56, lat: 35.565, type: "gcj02" },
],
},
// 绿区内的推荐点列表
places: [
{
lng: 139.56,
lat: 35.56,
type: "gcj02",
name: "user specified place in zone",
displayName: "user specified place in zone",
},
{
lng: 139.562,
lat: 35.562,
type: "gcj02",
name: "recommend place 2",
displayName: "recommend place 2",
},
],
},
});
};
return () => (
<BusinessReselectPlaceMap
class={"demo"}
unavailableTitle={"当前区域暂未开通服务"}
forbiddenTitle={"当前区域不可叫车"}
emptyTitle={"当前位置"}
queryingTitle={"正在获取地址信息"}
recomendDescription={"您将在此处上车"}
// 对于同一个 geoErrorOnceNotificationKey
// geoErrorOnce 事件在 geoErrorOnceNotificationInterval 时间间隔内全局只会触发一次
geoErrorOnceNotificationKey="BusinessReselectPlaceMap"
defaultPlace={{
lng: 139.777777,
lat: 35.777777,
type: "gcj02",
name: "default place name",
displayName: "default place display name",
}}
getAvailable={() => Promise.resolve(true)}
getRecomendPlace={async ({ lng, lat, type }) => {
// 向后端获取推荐点信息
return {
// 服务是否可用
available: true,
// 推荐类别
type: "Restricted",
// 绿区
zone: {
name: "绿区名称",
path: [
{ lng: lng - 0.001, lat: lat + 0.001, type },
{ lng: lng, lat: lat - 0.001, type },
{ lng: lng + 0.001, lat: lat + 0.0005, type },
],
},
// 推荐点列表
places: [
{
lat: lat - 0.00001,
lng: lng + 0.0001,
type,
name: "place 1",
displayName: "place 1",
},
{
lat: lat - 0.0002,
lng: lng + 0.0002,
type,
name: "place 2",
displayName: "place 2",
},
{
lat: lat - 0.0002,
lng: lng - 0.0001,
type,
name: "place 3",
displayName: "place 3",
},
],
};
}}
onChangeRecomandPlace={({ place, inputPlace, type, zone }) => {
console.log("用户操作地图,计算推荐点后得出的最终位置时触发,此时可以向后端查询城市信息");
console.log(
"计算推荐点之前的地址是: ",
inputPlace,
" 最终的地址是: ",
place,
" 推荐类型: ",
type,
"绿区(或红区):",
zone,
);
}}
onClickLocatorText={() =>
console.log("用户点击了蓝色光标文字触发,此时可以执行用户点击起点输入框相同的逻辑")
}
onClickLocatorPhoto={() =>
console.log("用户点击了蓝色光标图片触发,此时可以执行用户点击起点输入框相同的逻辑")
}
onGeoError={({ isBusinessTimeout }) => {
console.log(
"获取GPS失败时触发,此时可以弹框告诉用户",
isBusinessTimeout ? "给用户超时提示" : "给用户无权限反馈",
);
}}
/>
);
},
});
对于 jsx/tsx 文件的例子
import { defineComponent } from "vue";
import { BusinessQuotingMap, useBusinessQuotingMap } from "@heycar/heycars-map";
export default defineComponent({
setup() {
const { mapContext, toAmapCoordinateType } = useBusinessQuotingMap();
return () => (
<div>
<BusinessQuotingMap
class={"demo"}
from={{
displayName: "The Malayan Council",
name: "10 Winstedt Rd, #01-17, Singapore 227977",
type: "wgs84",
lat: 1.311295,
lng: 103.841974,
}}
to={{
displayName: "CDG Engie Charging Station",
name: "21 Kent Ridge Rd, Singapore 119220",
type: "wgs84",
lat: 1.2966426,
lng: 103.7763939,
}}
fromDescription={"您将在此上车"}
renderDescription={({ distance, duration, tolls }) =>
`全程 *${distance / 1000}公里* 约行驶 *${duration}* 高速费用 *${tolls ?? 0}*元`
}
mapContext={mapContext}
onClickStartPoint={(place) => console.log("点击起点时触发 palce = ", place)}
onClickEndPoint={(place) => console.log("点击终点时触发 palce = ", place)}
onChangeGoogleConnection={(status) =>
// 根据目前的产品需求,连通性检测只会被检测一次。
console.log("谷歌连通性发生变化时触发,连通性状态是:", status)
}
/>
<div>
经度 103.841974, 纬度 1.311295 在高德地图上的坐标类型是:
{toAmapCoordinateType([103.841974, 1.311295])}
</div>
<div>
);
},
});
对于 jsx/tsx 文件的例子
import { defineComponent } from "vue";
import { BusinessTaxiServiceMap, useBusinessTaxiServiceMap } from "@heycar/heycars-map";
export default defineComponent({
setup() {
const { mapContext } = useBusinessTaxiServiceMap();
return () => (
<BusinessTaxiServiceMap
class={css.adjustedDemo}
from={{
displayName: "The Malayan Council",
name: "10 Winstedt Rd, #01-17, Singapore 227977",
type: "wgs84",
lat: 1.311295,
lng: 103.841974,
}}
to={{
displayName: "CDG Engie Charging Station",
name: "21 Kent Ridge Rd, Singapore 119220",
type: "wgs84",
lat: 1.2966426,
lng: 103.7763939,
}}
driverStatus={"driverArrived"}
bookDispatchingTitle="2月14日 11:00 用车"
dispatchingTitle="正在为您搜索附近司机"
driverArrivedTitle="司机已等待 00:35"
renderStartSerivceTitle={({ distance, duration }) =>
`距你*${distance}*公里, *${duration}*分钟`
}
renderInServiceTitle={({ distance, duration }) =>
`距离终点*${distance}*公里, 预计*${duration}*分钟`
}
getDriverPositionTrack={async () => {
// 向后端请求司机的历史轨迹
return Promise.resolve<TrackPoint[]>([
{ lng: 121.4036983, lat: 31.216324, type: "gcj02", angle: 30, timestamp: 1698058438000 },
{ lng: 121.4036983, lat: 31.216324, type: "gcj02", angle: 30, timestamp: 1698058439000 },
{ lng: 121.403581, lat: 31.216415, type: "gcj02", angle: 30, timestamp: 1698058442000 },
]);
}}
interval={5000}
mapContext={mapContext}
/>
);
},
});
对于 jsx/tsx 文件的例子
import { defineComponent } from "vue";
import { BusinessTaxiEndMap } from "@heycar/heycars-map";
export default defineComponent({
setup() {
return () => (
<BusinessTaxiEndMap
class={"demo"}
from={{
displayName: "樟宜机场",
name: "樟宜机场",
lat: 1.35019,
lng: 103.994003,
type: "wgs84",
}}
to={{
lat: 1.346493,
lng: 103.746209,
name: "长城美华 Coffee Shop (CCMW)",
displayName: "长城美华 Coffee Shop (CCMW)",
type: "wgs84",
}}
/>
);
},
});
下列是一些更加基础的业务组件, 虽然导出了,但是目前阶段不推荐使用
AbsoluteAddressBox
DrivingLine
PassengerCircle
PlaceCircle
StartEndPoint
TaxiCar
WalkingLine
WaveCircle
DrivingRoute
WalkingRoute
PickupPoints
<HeycarMap center={[0, 0]} zoom={3}>
...
<AddressBox position={[0, 0]} title={"Martyrs Lawn"} description={"您将在此处上车"} />
...
</HeycarMap>
- 对于 vue 文件的例子
<HeycarMap :center="[0, 0]" class="any class name" :zoom="3">
<template #fallback>error</template>
<template #loading>loading</template>
...
<AddressBox title="Martyrs Lawn" :position="[0, 0]" description="您将在此处上车" >
</AddressBox>
...
</HeycarMap>
- 对于 jsx/tsx 文件的例子
<HeycarMap
class="any class name"
// 地图加载失败要显示的内容
fallback={() => <div>error</div>}
// 地图还没有加载完成时要显示的内容
loading={() => <div>loading</div>}
// 地图中心点
center={[0, 0]}
// 地图放缩
zoom={3}
>
...
</HeycarMap>
- 对于 vue 文件的例子
<HeycarMap :center="[0, 0]" class="any class name" :zoom="3">
<template #fallback>error</template>
<template #loading>loading</template>
...
</HeycarMap>
enableAuxiliaryGraspRoad 模式适用场景:
- 测试司机位置偏移 的优化效果
- 目前只能在国内打车,测试优化效果。
- 目前只能用 splytech 供应商进行测试。
测试时的注意点:
- 起点和终点 在 splytech 后台系统地图上的位置是错误的。
为了方便测试 司机位置偏移 的优化效果,在 enableAuxiliaryGraspRoad 模式下会显示下列辅助信息:
- 会显示两个导航路线,蓝色跟设计稿样式一致的是原来的导航路径,红色的是司机位置纠偏以后计算的导航路径。
- 会显示两个车辆,跟设计稿样式一致的是原来的车辆,虚影的车辆是司机位置纠偏以后的车辆。
- 会将供应商提供的司机位置显示为蓝色标记,辅助观察供应商提供的司机位置。
- 会显示一条黑色的路径,表示将供应商提供的位置纠偏以后的路径。
- enableAuxiliaryGraspRoad 模式下,为了方便测试,取消了屏幕 15 秒自动调整功能。
import { isCoordinatePointEqual } from "@heycar/heycars-map";
// 比较两个带有坐标类型的点是否为同一个点
isCoordinatePointEqual(
{ type: "gcj02", lng: 114.215114, lat: 22.215972 },
{ type: "wgs84", lng: 114.21016180538568, lat: 22.219570659901557 },
) === true;