From 98b801cae9c7a957469ef61121878d81a09f4cfe Mon Sep 17 00:00:00 2001
From: Matsuda <yiyth.fcb6@gmail.com>
Date: Thu, 21 Nov 2024 10:17:43 +0900
Subject: [PATCH] feat(location): support Map (#30648)

### Issue # (if applicable)

Closes #30647.

### Reason for this change
In [aws-location-alpha](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-location-alpha-readme.html), `map` has not been supported yet.



### Description of changes
Add `Map` class.



### Description of how you validated changes
Add a unit test and a integ test.



### Checklist
- [x] My code adheres to the [CONTRIBUTING GUIDE](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md) and [DESIGN GUIDELINES](https://github.com/aws/aws-cdk/blob/main/docs/DESIGN_GUIDELINES.md)

----

*By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
---
 .../@aws-cdk/aws-location-alpha/README.md     |  30 ++
 .../@aws-cdk/aws-location-alpha/lib/index.ts  |   1 +
 .../@aws-cdk/aws-location-alpha/lib/map.ts    | 333 ++++++++++++++++++
 ...efaultTestDeployAssert46CB2F9C.assets.json |  19 +
 ...aultTestDeployAssert46CB2F9C.template.json |  36 ++
 .../cdk-integ-location-map.assets.json        |  19 +
 .../cdk-integ-location-map.template.json      |  52 +++
 .../test/integ.map.js.snapshot/cdk.out        |   1 +
 .../test/integ.map.js.snapshot/integ.json     |  12 +
 .../test/integ.map.js.snapshot/manifest.json  | 113 ++++++
 .../test/integ.map.js.snapshot/tree.json      | 133 +++++++
 .../aws-location-alpha/test/integ.map.ts      |  24 ++
 .../aws-location-alpha/test/map.test.ts       | 122 +++++++
 13 files changed, 895 insertions(+)
 create mode 100644 packages/@aws-cdk/aws-location-alpha/lib/map.ts
 create mode 100644 packages/@aws-cdk/aws-location-alpha/test/integ.map.js.snapshot/MapTestDefaultTestDeployAssert46CB2F9C.assets.json
 create mode 100644 packages/@aws-cdk/aws-location-alpha/test/integ.map.js.snapshot/MapTestDefaultTestDeployAssert46CB2F9C.template.json
 create mode 100644 packages/@aws-cdk/aws-location-alpha/test/integ.map.js.snapshot/cdk-integ-location-map.assets.json
 create mode 100644 packages/@aws-cdk/aws-location-alpha/test/integ.map.js.snapshot/cdk-integ-location-map.template.json
 create mode 100644 packages/@aws-cdk/aws-location-alpha/test/integ.map.js.snapshot/cdk.out
 create mode 100644 packages/@aws-cdk/aws-location-alpha/test/integ.map.js.snapshot/integ.json
 create mode 100644 packages/@aws-cdk/aws-location-alpha/test/integ.map.js.snapshot/manifest.json
 create mode 100644 packages/@aws-cdk/aws-location-alpha/test/integ.map.js.snapshot/tree.json
 create mode 100644 packages/@aws-cdk/aws-location-alpha/test/integ.map.ts
 create mode 100644 packages/@aws-cdk/aws-location-alpha/test/map.test.ts

diff --git a/packages/@aws-cdk/aws-location-alpha/README.md b/packages/@aws-cdk/aws-location-alpha/README.md
index 7caa3522d4b11..0afe2c9e1c40e 100644
--- a/packages/@aws-cdk/aws-location-alpha/README.md
+++ b/packages/@aws-cdk/aws-location-alpha/README.md
@@ -24,6 +24,36 @@ global, trusted providers Esri and HERE. With affordable data, tracking and geof
 capabilities, and built-in metrics for health monitoring, you can build sophisticated
 location-enabled applications.
 
+## Map
+
+The Amazon Location Service Map resource gives you access to the underlying basemap data for a map.
+You use the Map resource with a map rendering library to add an interactive map to your application.
+You can add other functionality to your map, such as markers (or pins), routes, and polygon areas, as needed for your application.
+
+For information about how to use map resources in practice, see [Using Amazon Location Maps in your application](https://docs.aws.amazon.com/location/latest/developerguide/using-maps.html).
+
+To create a map, define a `Map`:
+
+```ts
+new location.Map(this, 'Map', {
+  mapName: 'my-map',
+  style: location.Style.VECTOR_ESRI_NAVIGATION,
+  customLayers: [location.CustomLayer.POI],
+});
+```
+
+Use the `grant()` or `grantRendering()` method to grant the given identity permissions to perform actions
+on the map:
+
+```ts
+declare const role: iam.Role;
+
+const map = new location.Map(this, 'Map', {
+  style: location.Style.VECTOR_ESRI_NAVIGATION,
+});
+map.grantRendering(role);
+```
+
 ## Place Index
 
 A key function of Amazon Location Service is the ability to search the geolocation information.
diff --git a/packages/@aws-cdk/aws-location-alpha/lib/index.ts b/packages/@aws-cdk/aws-location-alpha/lib/index.ts
index fe42b8efee131..871e150d2d823 100644
--- a/packages/@aws-cdk/aws-location-alpha/lib/index.ts
+++ b/packages/@aws-cdk/aws-location-alpha/lib/index.ts
@@ -1,4 +1,5 @@
 export * from './geofence-collection';
+export * from './map';
 export * from './place-index';
 export * from './route-calculator';
 export * from './tracker';
diff --git a/packages/@aws-cdk/aws-location-alpha/lib/map.ts b/packages/@aws-cdk/aws-location-alpha/lib/map.ts
new file mode 100644
index 0000000000000..3f9a9078163b7
--- /dev/null
+++ b/packages/@aws-cdk/aws-location-alpha/lib/map.ts
@@ -0,0 +1,333 @@
+import * as iam from 'aws-cdk-lib/aws-iam';
+import { ArnFormat, IResource, Lazy, Resource, Stack, Token } from 'aws-cdk-lib/core';
+import { Construct } from 'constructs';
+import { CfnMap } from 'aws-cdk-lib/aws-location';
+import { generateUniqueId } from './util';
+
+/**
+ * Represents the Amazon Location Service Map
+ */
+export interface IMap extends IResource {
+  /**
+   * The name of the map
+   *
+   * @attribute
+   */
+  readonly mapName: string;
+
+  /**
+   * The Amazon Resource Name (ARN) of the Map
+   *
+   * @attribute Arn, MapArn
+   */
+  readonly mapArn: string;
+}
+
+/**
+ * Properties for the Amazon Location Service Map
+ */
+export interface MapProps {
+  /**
+   * A name for the map
+   *
+   * Must be between 1 and 100 characters and contain only alphanumeric characters,
+   * hyphens, periods and underscores.
+   *
+   * @default - A name is automatically generated
+   */
+  readonly mapName?: string;
+
+  /**
+   * A description for the map
+   *
+   * @default - no description
+   */
+  readonly description?: string;
+
+  /**
+   * Specifies the map style selected from an available data provider.
+   */
+  readonly style: Style;
+
+  /**
+   * Specifies the custom layers for the style.
+   *
+   * @default - no custom layes
+   */
+  readonly customLayers?: CustomLayer[];
+
+  /**
+   * Specifies the map political view selected from an available data provider.
+   *
+   * The political view must be used in compliance with applicable laws, including those laws about mapping of the country or region where the maps,
+   * images, and other data and third-party content which you access through Amazon Location Service is made available.
+   *
+   * @see https://docs.aws.amazon.com/location/latest/developerguide/map-concepts.html#political-views
+   *
+   * @default - no political view
+   */
+  readonly politicalView?: PoliticalView;
+}
+
+/**
+ * An additional layer you can enable for a map style.
+ */
+export enum CustomLayer {
+  /**
+   * The POI custom layer adds a richer set of places, such as shops, services, restaurants, attractions, and other points of interest to your map.
+   * Currently only the VectorEsriNavigation map style supports the POI custom layer.
+   */
+  POI = 'POI',
+}
+
+/**
+ * The map style selected from an available data provider.
+ *
+ * @see https://docs.aws.amazon.com/location/latest/developerguide/what-is-data-provider.html
+ */
+export enum Style {
+
+  /**
+   * The Esri Navigation map style, which provides a detailed basemap for the world symbolized
+   * with a custom navigation map style that's designed for use during the day in mobile devices.
+   * It also includes a richer set of places, such as shops, services, restaurants, attractions,
+   * and other points of interest. Enable the POI layer by setting it in CustomLayers to leverage
+   * the additional places data.
+   */
+  VECTOR_ESRI_NAVIGATION = 'VectorEsriNavigation',
+
+  /**
+   * The Esri Imagery map style. A raster basemap that provides one meter or better
+   * satellite and aerial imagery in many parts of the world and lower resolution
+   * satellite imagery worldwide.
+   */
+  RASTER_ESRI_IMAGERY = 'RasterEsriImagery',
+
+  /**
+   * The Esri Light Gray Canvas map style, which provides a detailed vector basemap
+   * with a light gray, neutral background style with minimal colors, labels, and features
+   * that's designed to draw attention to your thematic content.
+   */
+  VECTOR_ESRI_LIGHT_GRAY_CANVAS = 'VectorEsriLightGrayCanvas',
+
+  /**
+   * The Esri Light map style, which provides a detailed vector basemap
+   * with a classic Esri map style.
+   */
+  VECTOR_ESRI_TOPOGRAPHIC = 'VectorEsriTopographic',
+
+  /**
+   * The Esri Street Map style, which provides a detailed vector basemap for the world
+   * symbolized with a classic Esri street map style. The vector tile layer is similar
+   * in content and style to the World Street Map raster map.
+   */
+  VECTOR_ESRI_STREETS = 'VectorEsriStreets',
+
+  /**
+   * The Esri Dark Gray Canvas map style. A vector basemap with a dark gray,
+   * neutral background with minimal colors, labels, and features that's designed
+   * to draw attention to your thematic content.
+   */
+  VECTOR_ESRI_DARK_GRAY_CANVAS = 'VectorEsriDarkGrayCanvas',
+
+  /**
+   * A default HERE map style containing a neutral, global map and its features
+   * including roads, buildings, landmarks, and water features. It also now includes
+   * a fully designed map of Japan.
+   */
+  VECTOR_HERE_EXPLORE = 'VectorHereExplore',
+
+  /**
+   * A global map containing high resolution satellite imagery.
+   */
+  RASTER_HERE_EXPLORE_SATELLITE = 'RasterHereExploreSatellite',
+
+  /**
+   * A global map displaying the road network, street names, and city labels
+   * over satellite imagery. This style will automatically retrieve both raster
+   * and vector tiles, and your charges will be based on total tiles retrieved.
+   */
+  HYBRID_HERE_EXPLORE_SATELLITE = 'HybridHereExploreSatellite',
+
+  /**
+   * The HERE Contrast (Berlin) map style is a high contrast detailed base map
+   * of the world that blends 3D and 2D rendering.
+   */
+  VECTOR_HERE_CONTRAST = 'VectorHereContrast',
+
+  /**
+   * A global map containing truck restrictions and attributes (e.g. width / height / HAZMAT)
+   * symbolized with highlighted segments and icons on top of HERE Explore to support
+   * use cases within transport and logistics.
+   */
+  VECTOR_HERE_EXPLORE_TRUCK = 'VectorHereExploreTruck',
+
+  /**
+   * The Grab Standard Light map style provides a basemap with detailed land use coloring,
+   * area names, roads, landmarks, and points of interest covering Southeast Asia.
+   */
+  VECTOR_GRAB_STANDARD_LIGHT = 'VectorGrabStandardLight',
+
+  /**
+   * The Grab Standard Dark map style provides a dark variation of the standard basemap
+   * covering Southeast Asia.
+   */
+  VECTOR_GRAB_STANDARD_DARK = 'VectorGrabStandardDark',
+
+  /**
+   * The Open Data Standard Light map style provides a detailed basemap for the world
+   * suitable for website and mobile application use. The map includes highways major roads,
+   * minor roads, railways, water features, cities, parks, landmarks, building footprints,
+   * and administrative boundaries.
+   */
+  VECTOR_OPEN_DATA_STANDARD_LIGHT = 'VectorOpenDataStandardLight',
+
+  /**
+   * Open Data Standard Dark is a dark-themed map style that provides a detailed basemap
+   * for the world suitable for website and mobile application use. The map includes highways
+   * major roads, minor roads, railways, water features, cities, parks, landmarks,
+   * building footprints, and administrative boundaries.
+   */
+  VECTOR_OPEN_DATA_STANDARD_DARK = 'VectorOpenDataStandardDark',
+
+  /**
+   * The Open Data Visualization Light map style is a light-themed style with muted colors
+   * and fewer features that aids in understanding overlaid data.
+   */
+  VECTOR_OPEN_DATA_VISUALIZATION_LIGHT = 'VectorOpenDataVisualizationLight',
+
+  /**
+   * The Open Data Visualization Dark map style is a dark-themed style with muted colors
+   * and fewer features that aids in understanding overlaid data.
+   */
+  VECTOR_OPEN_DATA_VISUALIZATION_DARK = 'VectorOpenDataVisualizationDark',
+
+}
+
+/**
+ * The map political view.
+ */
+export enum PoliticalView {
+  /**
+   * An India (IND) political view
+   */
+  INDIA = 'IND',
+}
+
+/**
+ * The Amazon Location Service Map
+ *
+ * @see https://docs.aws.amazon.com/location/latest/developerguide/map-concepts.html
+ */
+export class Map extends Resource implements IMap {
+  /**
+   * Use an existing map by name
+   */
+  public static fromMapName(scope: Construct, id: string, mapName: string): IMap {
+    const mapArn = Stack.of(scope).formatArn({
+      service: 'geo',
+      resource: 'map',
+      resourceName: mapName,
+    });
+
+    return Map.fromMapArn(scope, id, mapArn);
+  }
+
+  /**
+   * Use an existing map by ARN
+   */
+  public static fromMapArn(scope: Construct, id: string, mapArn: string): IMap {
+    const parsedArn = Stack.of(scope).splitArn(mapArn, ArnFormat.SLASH_RESOURCE_NAME);
+
+    if (!parsedArn.resourceName) {
+      throw new Error(`Map Arn ${mapArn} does not have a resource name.`);
+    }
+
+    class Import extends Resource implements IMap {
+      public readonly mapName = parsedArn.resourceName!;
+      public readonly mapArn = mapArn;
+    }
+
+    return new Import(scope, id, {
+      account: parsedArn.account,
+      region: parsedArn.region,
+    });
+  }
+
+  public readonly mapName: string;
+
+  public readonly mapArn: string;
+
+  /**
+   * The timestamp for when the map resource was created in ISO 8601 format
+   *
+   * @attribute
+   */
+  public readonly mapCreateTime: string;
+
+  /**
+   * The timestamp for when the map resource was last updated in ISO 8601 format
+   *
+   * @attribute
+   */
+  public readonly mapUpdateTime: string;
+
+  constructor(scope: Construct, id: string, props: MapProps) {
+    if (props.description && !Token.isUnresolved(props.description) && props.description.length > 1000) {
+      throw new Error(`\`description\` must be between 0 and 1000 characters, got: ${props.description.length} characters.`);
+    }
+
+    if (props.mapName !== undefined && !Token.isUnresolved(props.mapName)) {
+      if (props.mapName.length < 1 || props.mapName.length > 100) {
+        throw new Error(`\`mapName\` must be between 1 and 100 characters, got: ${props.mapName.length} characters.`);
+      }
+
+      if (!/^[-._\w]+$/.test(props.mapName)) {
+        throw new Error(`\`mapName\` must contain only alphanumeric characters, hyphens, periods and underscores, got: ${props.mapName}.`);
+      }
+    }
+
+    super(scope, id, {
+      physicalName: props.mapName ?? Lazy.string({ produce: () => generateUniqueId(this) }),
+    });
+
+    const map = new CfnMap(this, 'Resource', {
+      configuration: {
+        style: props.style,
+        customLayers: props.customLayers,
+        politicalView: props.politicalView,
+      },
+      mapName: this.physicalName,
+      description: props.description,
+    });
+
+    this.mapName = map.ref;
+    this.mapArn = map.attrArn;
+    this.mapCreateTime = map.attrCreateTime;
+    this.mapUpdateTime = map.attrUpdateTime;
+  }
+
+  /**
+   * Grant the given principal identity permissions to perform the actions on this map.
+   */
+  public grant(grantee: iam.IGrantable, ...actions: string[]): iam.Grant {
+    return iam.Grant.addToPrincipal({
+      grantee: grantee,
+      actions: actions,
+      resourceArns: [this.mapArn],
+    });
+  }
+
+  /**
+   * Grant the given identity permissions to rendering a map resource
+   * @See https://docs.aws.amazon.com/location/latest/developerguide/security_iam_id-based-policy-examples.html#security_iam_id-based-policy-examples-get-map-tiles
+   */
+  public grantRendering(grantee: iam.IGrantable): iam.Grant {
+    return this.grant(grantee,
+      'geo:GetMapTile',
+      'geo:GetMapSprites',
+      'geo:GetMapGlyphs',
+      'geo:GetMapStyleDescriptor',
+    );
+  }
+}
diff --git a/packages/@aws-cdk/aws-location-alpha/test/integ.map.js.snapshot/MapTestDefaultTestDeployAssert46CB2F9C.assets.json b/packages/@aws-cdk/aws-location-alpha/test/integ.map.js.snapshot/MapTestDefaultTestDeployAssert46CB2F9C.assets.json
new file mode 100644
index 0000000000000..9ad92102c9fa0
--- /dev/null
+++ b/packages/@aws-cdk/aws-location-alpha/test/integ.map.js.snapshot/MapTestDefaultTestDeployAssert46CB2F9C.assets.json
@@ -0,0 +1,19 @@
+{
+  "version": "36.0.0",
+  "files": {
+    "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22": {
+      "source": {
+        "path": "MapTestDefaultTestDeployAssert46CB2F9C.template.json",
+        "packaging": "file"
+      },
+      "destinations": {
+        "current_account-current_region": {
+          "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}",
+          "objectKey": "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22.json",
+          "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}"
+        }
+      }
+    }
+  },
+  "dockerImages": {}
+}
\ No newline at end of file
diff --git a/packages/@aws-cdk/aws-location-alpha/test/integ.map.js.snapshot/MapTestDefaultTestDeployAssert46CB2F9C.template.json b/packages/@aws-cdk/aws-location-alpha/test/integ.map.js.snapshot/MapTestDefaultTestDeployAssert46CB2F9C.template.json
new file mode 100644
index 0000000000000..ad9d0fb73d1dd
--- /dev/null
+++ b/packages/@aws-cdk/aws-location-alpha/test/integ.map.js.snapshot/MapTestDefaultTestDeployAssert46CB2F9C.template.json
@@ -0,0 +1,36 @@
+{
+ "Parameters": {
+  "BootstrapVersion": {
+   "Type": "AWS::SSM::Parameter::Value<String>",
+   "Default": "/cdk-bootstrap/hnb659fds/version",
+   "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]"
+  }
+ },
+ "Rules": {
+  "CheckBootstrapVersion": {
+   "Assertions": [
+    {
+     "Assert": {
+      "Fn::Not": [
+       {
+        "Fn::Contains": [
+         [
+          "1",
+          "2",
+          "3",
+          "4",
+          "5"
+         ],
+         {
+          "Ref": "BootstrapVersion"
+         }
+        ]
+       }
+      ]
+     },
+     "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI."
+    }
+   ]
+  }
+ }
+}
\ No newline at end of file
diff --git a/packages/@aws-cdk/aws-location-alpha/test/integ.map.js.snapshot/cdk-integ-location-map.assets.json b/packages/@aws-cdk/aws-location-alpha/test/integ.map.js.snapshot/cdk-integ-location-map.assets.json
new file mode 100644
index 0000000000000..db96b9213ce8c
--- /dev/null
+++ b/packages/@aws-cdk/aws-location-alpha/test/integ.map.js.snapshot/cdk-integ-location-map.assets.json
@@ -0,0 +1,19 @@
+{
+  "version": "36.0.0",
+  "files": {
+    "53db70e2c3a7b420db66cd2d99a17e4145e94dcb2126ef2904a9c4a5f790efe4": {
+      "source": {
+        "path": "cdk-integ-location-map.template.json",
+        "packaging": "file"
+      },
+      "destinations": {
+        "current_account-current_region": {
+          "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}",
+          "objectKey": "53db70e2c3a7b420db66cd2d99a17e4145e94dcb2126ef2904a9c4a5f790efe4.json",
+          "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}"
+        }
+      }
+    }
+  },
+  "dockerImages": {}
+}
\ No newline at end of file
diff --git a/packages/@aws-cdk/aws-location-alpha/test/integ.map.js.snapshot/cdk-integ-location-map.template.json b/packages/@aws-cdk/aws-location-alpha/test/integ.map.js.snapshot/cdk-integ-location-map.template.json
new file mode 100644
index 0000000000000..c3ced5977f561
--- /dev/null
+++ b/packages/@aws-cdk/aws-location-alpha/test/integ.map.js.snapshot/cdk-integ-location-map.template.json
@@ -0,0 +1,52 @@
+{
+ "Resources": {
+  "Map207736EC": {
+   "Type": "AWS::Location::Map",
+   "Properties": {
+    "Configuration": {
+     "CustomLayers": [
+      "POI"
+     ],
+     "PoliticalView": "IND",
+     "Style": "VectorEsriNavigation"
+    },
+    "Description": "my map for test",
+    "MapName": "my-map"
+   }
+  }
+ },
+ "Parameters": {
+  "BootstrapVersion": {
+   "Type": "AWS::SSM::Parameter::Value<String>",
+   "Default": "/cdk-bootstrap/hnb659fds/version",
+   "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]"
+  }
+ },
+ "Rules": {
+  "CheckBootstrapVersion": {
+   "Assertions": [
+    {
+     "Assert": {
+      "Fn::Not": [
+       {
+        "Fn::Contains": [
+         [
+          "1",
+          "2",
+          "3",
+          "4",
+          "5"
+         ],
+         {
+          "Ref": "BootstrapVersion"
+         }
+        ]
+       }
+      ]
+     },
+     "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI."
+    }
+   ]
+  }
+ }
+}
\ No newline at end of file
diff --git a/packages/@aws-cdk/aws-location-alpha/test/integ.map.js.snapshot/cdk.out b/packages/@aws-cdk/aws-location-alpha/test/integ.map.js.snapshot/cdk.out
new file mode 100644
index 0000000000000..1f0068d32659a
--- /dev/null
+++ b/packages/@aws-cdk/aws-location-alpha/test/integ.map.js.snapshot/cdk.out
@@ -0,0 +1 @@
+{"version":"36.0.0"}
\ No newline at end of file
diff --git a/packages/@aws-cdk/aws-location-alpha/test/integ.map.js.snapshot/integ.json b/packages/@aws-cdk/aws-location-alpha/test/integ.map.js.snapshot/integ.json
new file mode 100644
index 0000000000000..f8ada3a714711
--- /dev/null
+++ b/packages/@aws-cdk/aws-location-alpha/test/integ.map.js.snapshot/integ.json
@@ -0,0 +1,12 @@
+{
+  "version": "36.0.0",
+  "testCases": {
+    "MapTest/DefaultTest": {
+      "stacks": [
+        "cdk-integ-location-map"
+      ],
+      "assertionStack": "MapTest/DefaultTest/DeployAssert",
+      "assertionStackName": "MapTestDefaultTestDeployAssert46CB2F9C"
+    }
+  }
+}
\ No newline at end of file
diff --git a/packages/@aws-cdk/aws-location-alpha/test/integ.map.js.snapshot/manifest.json b/packages/@aws-cdk/aws-location-alpha/test/integ.map.js.snapshot/manifest.json
new file mode 100644
index 0000000000000..53433cfb7cd0d
--- /dev/null
+++ b/packages/@aws-cdk/aws-location-alpha/test/integ.map.js.snapshot/manifest.json
@@ -0,0 +1,113 @@
+{
+  "version": "36.0.0",
+  "artifacts": {
+    "cdk-integ-location-map.assets": {
+      "type": "cdk:asset-manifest",
+      "properties": {
+        "file": "cdk-integ-location-map.assets.json",
+        "requiresBootstrapStackVersion": 6,
+        "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version"
+      }
+    },
+    "cdk-integ-location-map": {
+      "type": "aws:cloudformation:stack",
+      "environment": "aws://unknown-account/unknown-region",
+      "properties": {
+        "templateFile": "cdk-integ-location-map.template.json",
+        "terminationProtection": false,
+        "validateOnSynth": false,
+        "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}",
+        "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}",
+        "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/53db70e2c3a7b420db66cd2d99a17e4145e94dcb2126ef2904a9c4a5f790efe4.json",
+        "requiresBootstrapStackVersion": 6,
+        "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version",
+        "additionalDependencies": [
+          "cdk-integ-location-map.assets"
+        ],
+        "lookupRole": {
+          "arn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-${AWS::Region}",
+          "requiresBootstrapStackVersion": 8,
+          "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version"
+        }
+      },
+      "dependencies": [
+        "cdk-integ-location-map.assets"
+      ],
+      "metadata": {
+        "/cdk-integ-location-map/Map/Resource": [
+          {
+            "type": "aws:cdk:logicalId",
+            "data": "Map207736EC"
+          }
+        ],
+        "/cdk-integ-location-map/BootstrapVersion": [
+          {
+            "type": "aws:cdk:logicalId",
+            "data": "BootstrapVersion"
+          }
+        ],
+        "/cdk-integ-location-map/CheckBootstrapVersion": [
+          {
+            "type": "aws:cdk:logicalId",
+            "data": "CheckBootstrapVersion"
+          }
+        ]
+      },
+      "displayName": "cdk-integ-location-map"
+    },
+    "MapTestDefaultTestDeployAssert46CB2F9C.assets": {
+      "type": "cdk:asset-manifest",
+      "properties": {
+        "file": "MapTestDefaultTestDeployAssert46CB2F9C.assets.json",
+        "requiresBootstrapStackVersion": 6,
+        "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version"
+      }
+    },
+    "MapTestDefaultTestDeployAssert46CB2F9C": {
+      "type": "aws:cloudformation:stack",
+      "environment": "aws://unknown-account/unknown-region",
+      "properties": {
+        "templateFile": "MapTestDefaultTestDeployAssert46CB2F9C.template.json",
+        "terminationProtection": false,
+        "validateOnSynth": false,
+        "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}",
+        "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}",
+        "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22.json",
+        "requiresBootstrapStackVersion": 6,
+        "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version",
+        "additionalDependencies": [
+          "MapTestDefaultTestDeployAssert46CB2F9C.assets"
+        ],
+        "lookupRole": {
+          "arn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-${AWS::Region}",
+          "requiresBootstrapStackVersion": 8,
+          "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version"
+        }
+      },
+      "dependencies": [
+        "MapTestDefaultTestDeployAssert46CB2F9C.assets"
+      ],
+      "metadata": {
+        "/MapTest/DefaultTest/DeployAssert/BootstrapVersion": [
+          {
+            "type": "aws:cdk:logicalId",
+            "data": "BootstrapVersion"
+          }
+        ],
+        "/MapTest/DefaultTest/DeployAssert/CheckBootstrapVersion": [
+          {
+            "type": "aws:cdk:logicalId",
+            "data": "CheckBootstrapVersion"
+          }
+        ]
+      },
+      "displayName": "MapTest/DefaultTest/DeployAssert"
+    },
+    "Tree": {
+      "type": "cdk:tree",
+      "properties": {
+        "file": "tree.json"
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git a/packages/@aws-cdk/aws-location-alpha/test/integ.map.js.snapshot/tree.json b/packages/@aws-cdk/aws-location-alpha/test/integ.map.js.snapshot/tree.json
new file mode 100644
index 0000000000000..71920df42ef38
--- /dev/null
+++ b/packages/@aws-cdk/aws-location-alpha/test/integ.map.js.snapshot/tree.json
@@ -0,0 +1,133 @@
+{
+  "version": "tree-0.1",
+  "tree": {
+    "id": "App",
+    "path": "",
+    "children": {
+      "cdk-integ-location-map": {
+        "id": "cdk-integ-location-map",
+        "path": "cdk-integ-location-map",
+        "children": {
+          "Map": {
+            "id": "Map",
+            "path": "cdk-integ-location-map/Map",
+            "children": {
+              "Resource": {
+                "id": "Resource",
+                "path": "cdk-integ-location-map/Map/Resource",
+                "attributes": {
+                  "aws:cdk:cloudformation:type": "AWS::Location::Map",
+                  "aws:cdk:cloudformation:props": {
+                    "configuration": {
+                      "style": "VectorEsriNavigation",
+                      "customLayers": [
+                        "POI"
+                      ],
+                      "politicalView": "IND"
+                    },
+                    "description": "my map for test",
+                    "mapName": "my-map"
+                  }
+                },
+                "constructInfo": {
+                  "fqn": "aws-cdk-lib.aws_location.CfnMap",
+                  "version": "0.0.0"
+                }
+              }
+            },
+            "constructInfo": {
+              "fqn": "aws-cdk-lib.Resource",
+              "version": "0.0.0"
+            }
+          },
+          "BootstrapVersion": {
+            "id": "BootstrapVersion",
+            "path": "cdk-integ-location-map/BootstrapVersion",
+            "constructInfo": {
+              "fqn": "aws-cdk-lib.CfnParameter",
+              "version": "0.0.0"
+            }
+          },
+          "CheckBootstrapVersion": {
+            "id": "CheckBootstrapVersion",
+            "path": "cdk-integ-location-map/CheckBootstrapVersion",
+            "constructInfo": {
+              "fqn": "aws-cdk-lib.CfnRule",
+              "version": "0.0.0"
+            }
+          }
+        },
+        "constructInfo": {
+          "fqn": "aws-cdk-lib.Stack",
+          "version": "0.0.0"
+        }
+      },
+      "MapTest": {
+        "id": "MapTest",
+        "path": "MapTest",
+        "children": {
+          "DefaultTest": {
+            "id": "DefaultTest",
+            "path": "MapTest/DefaultTest",
+            "children": {
+              "Default": {
+                "id": "Default",
+                "path": "MapTest/DefaultTest/Default",
+                "constructInfo": {
+                  "fqn": "constructs.Construct",
+                  "version": "10.3.0"
+                }
+              },
+              "DeployAssert": {
+                "id": "DeployAssert",
+                "path": "MapTest/DefaultTest/DeployAssert",
+                "children": {
+                  "BootstrapVersion": {
+                    "id": "BootstrapVersion",
+                    "path": "MapTest/DefaultTest/DeployAssert/BootstrapVersion",
+                    "constructInfo": {
+                      "fqn": "aws-cdk-lib.CfnParameter",
+                      "version": "0.0.0"
+                    }
+                  },
+                  "CheckBootstrapVersion": {
+                    "id": "CheckBootstrapVersion",
+                    "path": "MapTest/DefaultTest/DeployAssert/CheckBootstrapVersion",
+                    "constructInfo": {
+                      "fqn": "aws-cdk-lib.CfnRule",
+                      "version": "0.0.0"
+                    }
+                  }
+                },
+                "constructInfo": {
+                  "fqn": "aws-cdk-lib.Stack",
+                  "version": "0.0.0"
+                }
+              }
+            },
+            "constructInfo": {
+              "fqn": "@aws-cdk/integ-tests-alpha.IntegTestCase",
+              "version": "0.0.0"
+            }
+          }
+        },
+        "constructInfo": {
+          "fqn": "@aws-cdk/integ-tests-alpha.IntegTest",
+          "version": "0.0.0"
+        }
+      },
+      "Tree": {
+        "id": "Tree",
+        "path": "Tree",
+        "constructInfo": {
+          "fqn": "constructs.Construct",
+          "version": "10.3.0"
+        }
+      }
+    },
+    "constructInfo": {
+      "fqn": "aws-cdk-lib.App",
+      "version": "0.0.0"
+    }
+  }
+}
\ No newline at end of file
diff --git a/packages/@aws-cdk/aws-location-alpha/test/integ.map.ts b/packages/@aws-cdk/aws-location-alpha/test/integ.map.ts
new file mode 100644
index 0000000000000..76137c52ad533
--- /dev/null
+++ b/packages/@aws-cdk/aws-location-alpha/test/integ.map.ts
@@ -0,0 +1,24 @@
+import { App, Stack } from 'aws-cdk-lib';
+import * as integ from '@aws-cdk/integ-tests-alpha';
+import { Construct } from 'constructs';
+import { CustomLayer, Map, PoliticalView, Style } from '../lib';
+
+class TestStack extends Stack {
+  constructor(scope: Construct, id: string) {
+    super(scope, id);
+
+    new Map(this, 'Map', {
+      mapName: 'my-map',
+      description: 'my map for test',
+      style: Style.VECTOR_ESRI_NAVIGATION,
+      customLayers: [CustomLayer.POI],
+      politicalView: PoliticalView.INDIA,
+    });
+  }
+}
+
+const app = new App();
+
+new integ.IntegTest(app, 'MapTest', {
+  testCases: [new TestStack(app, 'cdk-integ-location-map')],
+});
diff --git a/packages/@aws-cdk/aws-location-alpha/test/map.test.ts b/packages/@aws-cdk/aws-location-alpha/test/map.test.ts
new file mode 100644
index 0000000000000..89f182a169f87
--- /dev/null
+++ b/packages/@aws-cdk/aws-location-alpha/test/map.test.ts
@@ -0,0 +1,122 @@
+import { Match, Template } from 'aws-cdk-lib/assertions';
+import * as iam from 'aws-cdk-lib/aws-iam';
+import { Stack } from 'aws-cdk-lib';
+import { CustomLayer, Map, PoliticalView, Style } from '../lib';
+
+let stack: Stack;
+beforeEach(() => {
+  stack = new Stack();
+});
+
+test('create a map', () => {
+  new Map(stack, 'Map', {
+    mapName: 'my-map',
+    description: 'my map for test',
+    style: Style.VECTOR_ESRI_NAVIGATION,
+    customLayers: [CustomLayer.POI],
+    politicalView: PoliticalView.INDIA,
+  });
+
+  Template.fromStack(stack).hasResourceProperties('AWS::Location::Map', {
+    MapName: 'my-map',
+    Description: 'my map for test',
+    Configuration: {
+      CustomLayers: ['POI'],
+      PoliticalView: 'IND',
+      Style: 'VectorEsriNavigation',
+    },
+  });
+});
+
+test('creates a map with empty description', () => {
+  new Map(stack, 'Map', {
+    description: '',
+    style: Style.VECTOR_ESRI_NAVIGATION,
+  });
+
+  Template.fromStack(stack).hasResourceProperties('AWS::Location::Map', {
+    Description: '',
+  });
+});
+
+test('throws with invalid description', () => {
+  expect(() => new Map(stack, 'Map', {
+    description: 'a'.repeat(1001),
+    style: Style.VECTOR_ESRI_NAVIGATION,
+  })).toThrow('`description` must be between 0 and 1000 characters, got: 1001 characters.');
+});
+
+test('throws with invalid name', () => {
+  expect(() => new Map(stack, 'Map', {
+    mapName: 'inv@lid',
+    style: Style.VECTOR_ESRI_NAVIGATION,
+  })).toThrow('`mapName` must contain only alphanumeric characters, hyphens, periods and underscores, got: inv@lid.');
+});
+
+test.each(['', 'a'.repeat(101)])('throws with invalid name, got: %s', (mapName) => {
+  expect(() => new Map(stack, 'Map', {
+    mapName,
+    style: Style.VECTOR_ESRI_NAVIGATION,
+  })).toThrow(`\`mapName\` must be between 1 and 100 characters, got: ${mapName.length} characters.`);
+});
+
+test('grant rendering ', () => {
+  const map = new Map(stack, 'Map', {
+    style: Style.VECTOR_ESRI_NAVIGATION,
+  });
+
+  const role = new iam.Role(stack, 'Role', {
+    assumedBy: new iam.ServicePrincipal('foo'),
+  });
+
+  map.grantRendering(role);
+
+  Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', Match.objectLike({
+    PolicyDocument: Match.objectLike({
+      Statement: [
+        {
+          Action: [
+            'geo:GetMapTile',
+            'geo:GetMapSprites',
+            'geo:GetMapGlyphs',
+            'geo:GetMapStyleDescriptor',
+          ],
+          Effect: 'Allow',
+          Resource: {
+            'Fn::GetAtt': [
+              'Map207736EC',
+              'Arn',
+            ],
+          },
+        },
+      ],
+    }),
+  }));
+});
+
+test('import from arn', () => {
+  const mapArn = stack.formatArn({
+    service: 'geo',
+    resource: 'map',
+    resourceName: 'MyMap',
+  });
+  const map = Map.fromMapArn(stack, 'Map', mapArn);
+
+  // THEN
+  expect(map.mapName).toEqual('MyMap');
+  expect(map.mapArn).toEqual(mapArn);
+});
+
+test('import from name', () => {
+  // WHEN
+  const mapName = 'MyMap';
+  const map = Map.fromMapName(stack, 'Map', mapName);
+
+  // THEN
+  expect(map.mapName).toEqual(mapName);
+  expect(map.mapArn).toEqual(stack.formatArn({
+    service: 'geo',
+    resource: 'map',
+    resourceName: 'MyMap',
+  }));
+});