342 lines
11 KiB
JavaScript
342 lines
11 KiB
JavaScript
import DeveloperError from "../Core/DeveloperError.js";
|
|
import defined from "../Core/defined.js";
|
|
import Resource from "../Core/Resource.js";
|
|
import Cartesian3 from "../Core/Cartesian3.js";
|
|
import Cartographic from "../Core/Cartographic.js";
|
|
import Matrix3 from "../Core/Matrix3.js";
|
|
import Matrix4 from "../Core/Matrix4.js";
|
|
import CesiumMath from "../Core/Math.js";
|
|
import Transforms from "../Core/Transforms.js";
|
|
import Frozen from "../Core/Frozen.js";
|
|
import Ellipsoid from "../Core/Ellipsoid.js";
|
|
import GoogleMaps from "../Core/GoogleMaps.js";
|
|
import CubeMapPanorama from "./CubeMapPanorama.js";
|
|
|
|
const DEFAULT_TILE_SIZE = 600;
|
|
|
|
/**
|
|
* <div class="notice">
|
|
* This object is normally not instantiated directly, use {@link GoogleStreetViewCubeMapPanoramaProvider.fromUrl}.
|
|
* </div>
|
|
*
|
|
*
|
|
* Creates a {@link PanoramaProvider} which provides imagery from {@link https://developers.google.com/maps/documentation/streetview|Google Street View Static API} to be displayed in a panorama.
|
|
*
|
|
* @alias GoogleStreetViewCubeMapPanoramaProvider
|
|
* @constructor
|
|
*
|
|
* @see CubeMapPanorama
|
|
*
|
|
* @demo {@link https://sandcastle.cesium.com/index.html?id=google-streetview-panorama|Cesium Sandcastle Google Streetview Panorama}
|
|
*
|
|
*/
|
|
function GoogleStreetViewCubeMapPanoramaProvider(options) {
|
|
options = options ?? Frozen.EMPTY_OBJECT;
|
|
this._key = defined(options.key) ? options.key : GoogleMaps.defaultApiKey;
|
|
|
|
this._baseResource = Resource.createIfNeeded(
|
|
options.url ?? GoogleMaps.streetViewStaticApiEndpoint,
|
|
);
|
|
|
|
this._metadataResource = this._baseResource.getDerivedResource({
|
|
url: "streetview/metadata",
|
|
});
|
|
|
|
this._tileSize = options.tileSize ?? DEFAULT_TILE_SIZE;
|
|
}
|
|
|
|
Object.defineProperties(GoogleStreetViewCubeMapPanoramaProvider.prototype, {});
|
|
|
|
/**
|
|
* Gets the panorama primitive for a requested position and orientation.
|
|
* @param {object} options Object with the following properties:
|
|
* @param {Cartographic} options.cartographic The position to place the panorama in the scene.
|
|
* @param {string} [options.panoId] The panoramaId identifier for the image in the Google API. If not provided this will be looked up for the provided cartographic location.
|
|
* @param {number} [options.tileSize] - Optional tile size override (square).
|
|
* @param {string} [options.signature] - Optional signature for signed URLs. See {@link https://developers.google.com/maps/documentation/streetview/digital-signature} for more information.
|
|
* @param {Credit|string} [options.credit] A credit for the data source, which is displayed on the canvas.
|
|
*
|
|
* @returns {CubeMapPanorama} The panorama primitive textured with imagery.
|
|
*
|
|
* @example
|
|
*
|
|
* const provider = await Cesium.GoogleStreetViewCubeMapPanoramaProvider.fromUrl({
|
|
* key: 'your Google Streetview Static api key'
|
|
* })
|
|
*
|
|
* const panoIdObject = provider.getNearestPanoId(position);
|
|
* const position = Cartographic.fromDegrees(panoIdObject.location.lng, panoIdObject.location.lat, 0);
|
|
*
|
|
* const primitive = await provider.loadPanorama({
|
|
* cartographic: position,
|
|
* panoId: panoIdObject.panoId
|
|
* });
|
|
* viewer.scene.primitive.add(primitive);
|
|
*
|
|
*/
|
|
GoogleStreetViewCubeMapPanoramaProvider.prototype.loadPanorama =
|
|
async function (options) {
|
|
const cartographic = options.cartographic;
|
|
//>>includeStart('debug', pragmas.debug);
|
|
if (!defined(options.cartographic)) {
|
|
throw new DeveloperError("options.cartographic is required.");
|
|
}
|
|
//>>includeEnd('debug');
|
|
|
|
const tileSize = options.tileSize ?? this._tileSize;
|
|
const tileSizeString = `${tileSize}x${tileSize}`;
|
|
|
|
const signature = options.signature;
|
|
|
|
let { panoId } = options;
|
|
|
|
if (!defined(panoId)) {
|
|
const panoIdObject = await this.getNearestPanoId(cartographic);
|
|
panoId = panoIdObject.panoId;
|
|
}
|
|
|
|
const credit = GoogleMaps.getDefaultCredit();
|
|
|
|
const posObj = Cartesian3.fromDegrees(
|
|
CesiumMath.toDegrees(cartographic.longitude),
|
|
CesiumMath.toDegrees(cartographic.latitude),
|
|
0,
|
|
);
|
|
|
|
const facePromises = [
|
|
this._loadFaceImage({
|
|
heading: 0,
|
|
pitch: 0,
|
|
tileSizeString,
|
|
panoId,
|
|
signature,
|
|
}),
|
|
this._loadFaceImage({
|
|
heading: 180,
|
|
pitch: 0,
|
|
tileSizeString,
|
|
panoId,
|
|
signature,
|
|
}),
|
|
this._loadFaceImage({
|
|
heading: -90,
|
|
pitch: -90,
|
|
tileSizeString,
|
|
panoId,
|
|
signature,
|
|
}),
|
|
this._loadFaceImage({
|
|
heading: -90,
|
|
pitch: 90,
|
|
tileSizeString,
|
|
panoId,
|
|
signature,
|
|
}),
|
|
this._loadFaceImage({
|
|
heading: 270,
|
|
pitch: 0,
|
|
tileSizeString,
|
|
panoId,
|
|
signature,
|
|
}),
|
|
this._loadFaceImage({
|
|
heading: 90,
|
|
pitch: 0,
|
|
tileSizeString,
|
|
panoId,
|
|
signature,
|
|
}),
|
|
];
|
|
|
|
const [positiveX, negativeX, positiveY, negativeY, positiveZ, negativeZ] =
|
|
await Promise.all(facePromises);
|
|
|
|
const northDownEastToFixedFrameTransform =
|
|
Transforms.localFrameToFixedFrameGenerator("north", "down");
|
|
|
|
const transform = Matrix4.getMatrix3(
|
|
northDownEastToFixedFrameTransform(posObj, Ellipsoid.default),
|
|
new Matrix3(),
|
|
);
|
|
|
|
const panorama = new CubeMapPanorama({
|
|
sources: {
|
|
positiveX,
|
|
negativeX,
|
|
positiveY,
|
|
negativeY,
|
|
positiveZ,
|
|
negativeZ,
|
|
},
|
|
transform: transform,
|
|
credit,
|
|
});
|
|
|
|
return panorama;
|
|
};
|
|
|
|
/**
|
|
* Gets the panoIds for the given cartographic location. See {@link https://developers.google.com/maps/documentation/tile/streetview#panoid-search}.
|
|
|
|
* @param {Cartographic} position The position to search for the nearest panoId.
|
|
* @param {number} [radius=50] The radius in meters to search for the nearest panoId.
|
|
*
|
|
* @returns {Object} an object containing a panoId, latitude, and longitude of the closest panorama
|
|
*
|
|
* @example
|
|
*
|
|
* const provider = await Cesium.GoogleStreetViewCubeMapPanoramaProvider.fromUrl({
|
|
* key: 'your Google Streetview Static api key'
|
|
* })
|
|
* const panoIds = provider.getNearestPanoId(position);
|
|
*/
|
|
GoogleStreetViewCubeMapPanoramaProvider.prototype.getNearestPanoId =
|
|
async function (position, radius) {
|
|
//>>includeStart('debug', pragmas.debug);
|
|
if (!defined(position)) {
|
|
throw new DeveloperError("position is required.");
|
|
}
|
|
//>>includeEnd('debug');
|
|
|
|
const pos = [
|
|
CesiumMath.toDegrees(position.latitude),
|
|
CesiumMath.toDegrees(position.longitude),
|
|
];
|
|
const posString = pos.join(",");
|
|
|
|
const resource = this._metadataResource.getDerivedResource({
|
|
queryParameters: {
|
|
key: this._key,
|
|
location: posString,
|
|
radius: radius ?? 50,
|
|
},
|
|
});
|
|
|
|
const panoIdObject = await resource.fetchJson();
|
|
|
|
if (panoIdObject.status !== "OK") {
|
|
throw new DeveloperError(
|
|
`GoogleStreetViewCubeMapPanoramaProvider metadata error: ${panoIdObject.status}`,
|
|
);
|
|
}
|
|
return {
|
|
panoId: panoIdObject.pano_id,
|
|
latitude: panoIdObject.location.lat,
|
|
longitude: panoIdObject.location.lng,
|
|
};
|
|
};
|
|
|
|
/**
|
|
* Gets metadata for panoId. See {@link https://developers.google.com/maps/documentation/tile/streetview#metadata_response} for response object.
|
|
* @param {string} panoId
|
|
*
|
|
* @returns {object} object containing metadata for the panoId.
|
|
*
|
|
* @example
|
|
* const panoIdObject = provider.getNearestPanoId(position);
|
|
* const panoIdMetadata = provider.getPanoIdMetadata(panoIdObject.panoId);
|
|
*
|
|
*/
|
|
GoogleStreetViewCubeMapPanoramaProvider.prototype.getPanoIdMetadata =
|
|
async function (panoId) {
|
|
const resource = this._metadataResource.getDerivedResource({
|
|
queryParameters: {
|
|
key: this._key,
|
|
pano: panoId,
|
|
},
|
|
});
|
|
|
|
const panoIdObject = await resource.fetchJson();
|
|
|
|
if (panoIdObject.status !== "OK") {
|
|
throw new DeveloperError(
|
|
`GoogleStreetViewCubeMapPanoramaProvider metadata error: ${panoIdObject.status}`,
|
|
);
|
|
}
|
|
return panoIdObject;
|
|
};
|
|
|
|
GoogleStreetViewCubeMapPanoramaProvider.prototype._loadFaceImage =
|
|
async function (options) {
|
|
const url = this._buildFaceUrl(options);
|
|
|
|
try {
|
|
return await Resource.fetchImage({
|
|
url,
|
|
preferImageBitmap: true,
|
|
flipY: true,
|
|
});
|
|
} catch (error) {
|
|
throw new DeveloperError(`Failed to load Street View face image: ${url}`);
|
|
}
|
|
};
|
|
|
|
GoogleStreetViewCubeMapPanoramaProvider.prototype._buildFaceUrl = function (
|
|
options,
|
|
) {
|
|
const { heading, pitch, tileSizeString, panoId, signature } = options;
|
|
const resource = this._baseResource.getDerivedResource({
|
|
queryParameters: {
|
|
size: tileSizeString,
|
|
pano: panoId,
|
|
heading,
|
|
pitch,
|
|
key: this._key,
|
|
...(defined(signature) && { signature }),
|
|
},
|
|
});
|
|
|
|
return resource.url;
|
|
};
|
|
|
|
/**
|
|
* Creates a {@link PanoramaProvider} which provides cube face images from the {@link https://developers.google.com/maps/documentation/streetview|Google Street View Static API}.
|
|
* @param {object} options Object with the following properties:
|
|
* @param {string} [options.key=GoogleMaps.defaultStreetViewStaticApiKey] Your API key to access Google Street View Static API. See {@link https://developers.google.com/maps/documentation/javascript/get-api-key} for instructions on how to create your own key. If undefined, defaults to {@link GoogleMaps.defaultStreetViewStaticApiKey}. If that value is unavailable, falls back to {@link GoogleMaps.defaultApiKey}.
|
|
* @param {string|Resource} [options.url=GoogleMaps.streetViewStaticApiEndpoint] The URL to access Google Street View Static API. See {@link https://developers.google.com/maps/documentation/streetview/overview} for more information.
|
|
* @param {number} [options.tileSize=600] Default width and height (in pixels) of each square tile.
|
|
* @param {Credit|string} [options.credit] A credit for the data source, which is displayed on the canvas.
|
|
*
|
|
* @returns {Promise<GoogleStreetViewCubeMapPanoramaProvider>} A promise that resolves to the created GoogleStreetViewCubeMapPanoramaProvider.'
|
|
*
|
|
* @example
|
|
* const provider = await Cesium.GoogleStreetViewCubeMapPanoramaProvider.fromUrl({
|
|
* key: 'your Google Streetview Static api key'
|
|
* })
|
|
*/
|
|
GoogleStreetViewCubeMapPanoramaProvider.fromUrl = async function (options) {
|
|
options = options ?? {};
|
|
options.key =
|
|
options.key ??
|
|
GoogleMaps.defaultStreetViewStaticApiKey ??
|
|
GoogleMaps.defaultApiKey;
|
|
if (
|
|
!defined(options.key) &&
|
|
!defined(GoogleMaps.defaultStreetViewStaticApiKey) &&
|
|
!defined(GoogleMaps.defaultApiKey)
|
|
) {
|
|
throw new DeveloperError(
|
|
"options.key, GoogleMaps.defaultStreetViewStaticApiKey or GoogleMaps.defaultApiKey is required.",
|
|
);
|
|
}
|
|
|
|
return new GoogleStreetViewCubeMapPanoramaProvider({
|
|
...options,
|
|
});
|
|
};
|
|
|
|
GoogleStreetViewCubeMapPanoramaProvider._parseMetadata = function (
|
|
panoIdMetadata,
|
|
) {
|
|
const cartographic = Cartographic.fromDegrees(
|
|
panoIdMetadata.location.lng,
|
|
panoIdMetadata.location.lat,
|
|
0,
|
|
);
|
|
return {
|
|
cartographic,
|
|
};
|
|
};
|
|
|
|
export default GoogleStreetViewCubeMapPanoramaProvider;
|