npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@propmix/overlay-map

v0.1.0

Published

A standalone Angular component, `pmx-overlay-map`, that wraps a Google Map and adds the things apps usually need on top of it:

Readme

@propmix/overlay-map

A standalone Angular component, pmx-overlay-map, that wraps a Google Map and adds the things apps usually need on top of it:

  • Draw polygons by clicking on the map, and read the drawn shapes back out.
  • Edit and delete existing polygons (drag vertices, select, remove).
  • Draw a radius circle around the center point.
  • Plot typed markers (subject, comparable statuses, etc.) with colors and labels.
  • Show a custom info window (your own Angular component) when a marker is clicked.

1. Prerequisites

This library does not load the Google Maps JavaScript API for you. Your application must load it before the component is shown, and it must include the marker library (the component uses AdvancedMarkerElement).


2. Installation

npm install @propmix/overlay-map

3. Map ID setup (one time)

Google's AdvancedMarkerElement requires a Map ID. You can give the component a default Map ID once at the application root, and every pmx-overlay-map will pick it up.

import { provideOverlayMap } from '@propmix/overlay-map';

bootstrapApplication(AppComponent, {
  providers: [provideOverlayMap({ mapId: 'YOUR_MAP_ID' })],
});

If you use NgModules, add the same provider to your root module's providers array. You can still override the Map ID per instance with the [mapId] input (see below). If you need the value to come from a service (for example a company setting), provide the GOOGLE_MAP_ID token directly instead of using provideOverlayMap:

import { GOOGLE_MAP_ID } from '@propmix/overlay-map';

providers: [
  {
    provide: GOOGLE_MAP_ID,
    useFactory: (settingsService: SettingsService) => settingsService.mapId,
    deps: [SettingsService],
  },
];

4. Quick start

import { Component } from '@angular/core';
import { Coordinate, OverlayMapComponent } from '@propmix/overlay-map';

@Component({
  selector: 'app-demo',
  standalone: true,
  imports: [OverlayMapComponent],
  template: `
    <div style="height: 400px">
      <pmx-overlay-map [center]="center"></pmx-overlay-map>
    </div>
  `,
})
export class DemoComponent {
  center: Coordinate = { lat: 40.7128, lng: -74.006 };
}

IMPORTANT: The component fills its parent (height: 100%). Give the wrapping element a height, otherwise the map collapses to zero pixels and nothing is visible.


5. Inputs

| Input | Type | Default | Description | | --------------------- | ---------------------- | -------------- | ---------------------------------------------------------------------------------------- | | center | Coordinate | required | Initial map center. A subject marker (home icon) is always placed here. | | zoom | number | 14 | Initial zoom level. Ignored once the map fits bounds to a circle, polygons, or markers. | | readOnly | boolean | false | When true, drawing, editing, and deletion are all disabled. The map is view only. | | multiPolygon | MultiPolygon \| null | undefined | Polygons to render on load. Editable unless readOnly. The map fits its bounds to them. | | radius | number \| null | undefined | Draws a circle of this radius in miles around center and fits bounds to it. | | markerList | MarkerCoordinate[] | undefined | Markers to plot. The map fits bounds to them. | | hideInfoWindow | boolean | false | When true, clicking a marker does nothing (no info window opens). | | mapId | string | provided token | Overrides the app-level GOOGLE_MAP_ID for this one map. | | mapOptions | OverlayMapOptions | see defaults | Zoom limits, pan restriction bounds, and polygon colors. | | infoWindowComponent | Type<unknown> | undefined | The Angular component rendered inside a marker's info window. See section 8. |

All inputs except center are optional. multiPolygon, radius, and markerList are reactive: assigning a new value re-renders that layer and re-fits the map.

Output

| Output | Type | Description | | -------- | ------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | notify | EventEmitter<MapNotification> | Fires when the component wants the host to show a message (for example "Add at least 3 points to draw a polygon", or a map-load error). Surface it however you like (snackbar, toast, etc.). |


6. Data types

These are the interfaces used by the inputs and outputs. All are exported from @propmix/overlay-map.

Coordinate

A single latitude/longitude point. Values may be numbers or numeric strings.

interface Coordinate {
  lat: string | number;
  lng: string | number;
}

Polygon and MultiPolygon

A Polygon is the ordered list of corner points of one shape. A MultiPolygon is a list of those shapes, so a single map can hold several independent polygons.

type Polygon = Coordinate[]; // one shape: its vertices in order
type MultiPolygon = Polygon[]; // many shapes on the same map

MarkerType

The visual style of a marker. Each type maps to a built-in color.

type MarkerType = 'subject' | 'active' | 'pending' | 'foreclosure' | 'sold' | 'disabled';

| Type | Appearance | | ------------- | ----------------------------------- | | subject | Purple pin with house icon. Default | | active | Green | | pending | Amber | | foreclosure | Magenta | | sold | Red | | disabled | Gray |

MarkerCoordinate

A marker is a coordinate plus its presentation and an optional payload.

interface MarkerCoordinate extends Coordinate {
  type?: MarkerType; // which built-in style to use
  label: string; // text shown inside the marker (ignored for `subject`, which shows the house icon)
  isSelected?: boolean; // highlights the marker and raises its z-index
  id?: string; // stable identity; used to match/update markers and to find the clicked one
  color?: string; // overrides the type color (any CSS color). When selected, becomes the border color
  data?: unknown; // arbitrary payload handed to your info-window component (see section 8)
}

Tip: always set a unique id. The component matches markers by lat, lng, and id when markerList changes, so a stable id lets it update an existing marker instead of dropping and recreating it, and it is how the clicked marker is mapped back to its data.

OverlayMapOptions

Optional map-level tuning. Every field is optional; sensible defaults apply.

interface OverlayMapOptions {
  minZoom?: number; // default 3
  maxZoom?: number; // default 19
  gestureHandling?: string; // default 'greedy' (see Google Maps gestureHandling)
  restrictionBounds?: google.maps.LatLngBoundsLiteral; // limits how far the map can pan
  polygonStrokeColor?: string; // default '#000000' - normal polygon outline/fill
  polygonSelectedColor?: string; // default '#dc3545' - color of a polygon selected for deletion
}

MapNotification

The payload emitted by (notify).

type MapNotificationType = 'success' | 'info' | 'warning' | 'error';

interface MapNotification {
  type: MapNotificationType;
  message: string;
}

MapInfoWindowContext

What your info-window component receives. Covered in section 8.

interface MapInfoWindowContext<T = unknown> {
  data: T; // the clicked marker's `data` payload
  marker: MarkerCoordinate; // the full clicked marker
  close: () => void; // call to close the info window
}

7. Drawing polygons and reading them back

Drawing (interactive)

When the map is not readOnly, drawing is always armed:

  1. Click on the map to drop the first vertex, then keep clicking to add more.
  2. Finish the shape by either double-clicking, or clicking the first vertex again.
  3. A polygon needs at least 3 points. If the user tries to close it with fewer, the component emits a notify warning instead of closing.

You can draw several polygons in a row; each finished shape stays on the map and a new one starts on the next click.

Editing and deleting

  • Finished polygons are editable: drag a vertex to move it, or drag a midpoint to add one.
  • Click a polygon to select it. It turns red (polygonSelectedColor) and a round trash badge appears at its center.
  • Click the trash badge to delete that polygon. Click the polygon again to deselect without deleting.

Reading the drawn shapes: getPolygons()

This is the key method for capturing user input. Get a reference to the component with @ViewChild, then call getPolygons() whenever you want the current shapes (for example when the user clicks Save). It returns the live vertices of every polygon on the map, or null if there are none.

import { Component, ViewChild } from '@angular/core';
import { MultiPolygon, OverlayMapComponent } from '@propmix/overlay-map';

@Component({
  standalone: true,
  imports: [OverlayMapComponent],
  template: `
    <div style="height: 400px">
      <pmx-overlay-map [center]="center" [multiPolygon]="initialShapes"></pmx-overlay-map>
    </div>
    <button (click)="save()">Save</button>
  `,
})
export class BoundaryEditorComponent {
  @ViewChild(OverlayMapComponent) map!: OverlayMapComponent;

  center = { lat: 40.7128, lng: -74.006 };
  initialShapes: MultiPolygon | null = null;

  save(): void {
    const shapes: MultiPolygon | null = this.map.getPolygons();
    // `shapes` is the up-to-date geometry, including edits and newly drawn polygons.
    // Each coordinate is returned as { lat: number, lng: number }.
    console.log(shapes);
  }
}

getPolygons() always reflects the current state, so it includes vertex edits the user made after loading and any polygons they drew. Pass the same data back through [multiPolygon] later to restore the shapes.

Other public methods

| Method | Description | | -------------------------------------------- | ------------------------------------------------------------------------------------------ | | getPolygons(): MultiPolygon \| null | Returns the current polygon geometry, or null if none. | | openMarker(marker: MarkerCoordinate): void | Programmatically opens the info window for a marker (same effect as the user clicking it). | | deleteOverlay(): void | Removes all polygons and the circle, and clears any selection or in-progress drawing. |


8. Custom marker info windows

When a marker is clicked, the component can render your own Angular component inside the Google info window. This keeps the library agnostic about how your marker details should look.

Step 1. Build any standalone component. Inject MAP_INFO_WINDOW_DATA to receive the clicked marker's context (data, the full marker, and a close callback).

import { Component, inject } from '@angular/core';
import { MAP_INFO_WINDOW_DATA, MapInfoWindowContext } from '@propmix/overlay-map';

interface CompInfo {
  address: string;
  price: number;
}

@Component({
  selector: 'app-comp-info',
  standalone: true,
  template: `
    <h4>{{ info.address }}</h4>
    <p>{{ info.price | currency }}</p>
    <button (click)="ctx.close()">Close</button>
  `,
})
export class CompInfoComponent {
  private ctx: MapInfoWindowContext<CompInfo> = inject(MAP_INFO_WINDOW_DATA);
  info = this.ctx.data;
}

Step 2. Attach the payload to each marker via data, and pass the component to the map.

markers: MarkerCoordinate[] = [
  {
    lat: 40.72,
    lng: -74.0,
    type: 'sold',
    label: '$1.2M',
    id: 'comp-1',
    data: { address: '123 Main St', price: 1200000 } satisfies CompInfo,
  },
];

infoWindow = CompInfoComponent;
<pmx-overlay-map [center]="center" [markerList]="markers" [infoWindowComponent]="infoWindow"> </pmx-overlay-map>

When a marker is clicked, CompInfoComponent is created with that marker's data, rendered inside the info window, and destroyed when the window closes. Set [hideInfoWindow]="true" to turn marker clicks off entirely.


9. Example: Boundary editor in a dialog

This mirrors a common pattern: edit a property boundary in a modal and return the result.

import { Component, ViewChild } from '@angular/core';
import { MapNotification, MultiPolygon, OverlayMapComponent } from '@propmix/overlay-map';

@Component({
  standalone: true,
  imports: [OverlayMapComponent],
  template: `
    <div style="height: 70vh">
      <pmx-overlay-map
        [center]="center"
        [multiPolygon]="boundary"
        [readOnly]="readOnly"
        (notify)="onNotify($event)"
      ></pmx-overlay-map>
    </div>
    <button (click)="save()">Save</button>
  `,
})
export class BoundaryDialogComponent {
  @ViewChild(OverlayMapComponent) map!: OverlayMapComponent;

  center = { lat: 40.7128, lng: -74.006 };
  boundary: MultiPolygon | null = null;
  readOnly = false;

  onNotify(n: MapNotification): void {
    // forward to your snackbar/toast service
    console.log(n.type, n.message);
  }

  save(): void {
    const result = this.map.getPolygons();
    // close the dialog and return `result`
  }
}