=== Advanced Custom Fields: Multiple Coordinates ===
Contributors: jonashjalmarsson, web111se
Tags: acf, google maps, coordinates, map, points
Requires at least: 5.0
Tested up to: 7.1
Requires PHP: 7.0
Stable tag: 1.4.0
License: GPLv3 or later
License URI: https://www.gnu.org/licenses/gpl-3.0.html

A multi-point Google Maps field for Advanced Custom Fields: numbered points, reorder and remove them in a list, and draw a line or an area.

== Description ==

A multi-point Google Maps field for Advanced Custom Fields. Click the map to drop a point and drag it to move it. "Edit points" opens the list of points, where a point can be dragged to a new position or removed. Every point is numbered so the stored order is visible — it is the order the value is stored in and the order the line or area is drawn in — and the last change can always be undone.

The plugin reads your existing Google Maps API key from ACF's global setting (`google_api_key`), so no extra configuration is needed if you already use ACF Map fields. If you don't have one set, you can also provide a key via the `acfmc_gmaps_key` filter.

Originally inspired by the single-point [ACF: Coordinates](https://wordpress.org/plugins/advanced-custom-fields-coordinates/) field by Stupid Studio; this plugin extends the idea to multiple points per field.

Licensed under the GNU General Public License v3. See `license.txt` for details.


== Installation ==

Install this plugin by downloading [the source](https://wordpress.org/plugins/advanced-custom-fields-multiple-coordinates/) and unzipping it into the plugin folder in your WordPress installation. Make sure to also have ACF (Advanced Custom Fields) installed and active.


== Usage ==

When you create a new custom field with ACF, set the field type to **Coordinates map** (under Content). The coordinates chooser will then show up when you edit a post with your custom fields.

**Adding points.** Click anywhere on the map to drop a point. "Add point" drops one in the middle of the map, which is also the way to add points from the keyboard. Drag a point to move it.

**Searching for a place.** Type a place in the search field and press Enter: the map pans there and a point is dropped on the spot, carrying the name you searched for. Search for "Kalmar slott" and the point is called Kalmar slott, not the street address Google resolves it to. The name is stored with the point and shown in its popup. Points you click straight onto the map have no name — only searched points get one.

**Seeing what a point is.** Click a marker and a popup shows its number, its latitude and longitude, and its name when it has one. Nothing is looked up when you click; the popup only shows what is already stored.

**Editing the points.** "Edit points (N)" opens the list of points, and that is where a point is changed:

* Drag a row by its handle to move the point to another position. The order is not decoration — it is the order the value is stored in, the order `get_field()` returns and the order the line or area is drawn in, so the map is renumbered and redrawn as soon as you drop the row.
* The same handle works from the keyboard: tab to it and press the up or down arrow key.
* Press Remove on a row to delete that point.

**Undo.** The undo icon takes back the last change — an accidental point, a removed point, a reordered list, or a marker dragged to the wrong place. It goes back up to 30 steps, until the page is reloaded.

**Drawing a shape.** The first menu says *what* is drawn through the points: nothing, a **Line** or an **Area**. Pick either one and a second menu says *how* it is drawn:

* **Sharp** — straight segments from point to point, in stored order.
* **Smoothed** — a Catmull-Rom curve through every point. Google Maps has no curves of its own, so it is drawn as a dense polyline; an area closes the curve back to the first point.
* **Bounding box** — the rectangle the points span, ignoring the path between them.

A line is the outline only; an area is filled. So a bounding box drawn as a line is an empty rectangle around the points, and the same box drawn as an area is a filled one. Choosing a shape also reveals a colour picker and a **Markers** switch, and the map redraws as soon as you change any of them.

**The Markers switch.** It controls the front end: turn it off and your theme should draw only the line or the area, without the markers — which is the point of it, since a clean route or outline is often what you want. In the editor the markers stay visible, dimmed, because points you cannot see are points you cannot drag, click or reorder. The switch only appears once a shape is chosen; with no shape the markers are all there is to draw.

**The rest.** The `<>` icon reveals and selects the raw stored value so it can be pasted into another Coordinates map field, and the `i` icon lists what the field can do.

Saving works the way it always has: the value is stored as JSON in postmeta, so field groups and values created with earlier versions keep working unchanged.

**This plugin does not render anything on the front end.** It stores points; drawing them is the theme's job. ACF's own `[acf field="..."]` shortcode returns an empty string for this field type, so the examples below are the starting point — copy one into your theme and adjust it.

**Reading the value.** `get_field()` gives you the points in the order they are stored, plus the zoom level and, when a shape was chosen, the shape, how it is drawn, its colour and whether the markers should be drawn with it:

    <?php
    $values = get_field('*****FIELD_NAME*****');
    print_r($values);
    /* gives you something like:
        Array
        (
            [coords] => Array
                (
                    [0] => Array
                        (
                            [lat] => 57.156363766336
                            [lng] => 16.364327427978
                        )
                    [1] => Array
                        (
                            [lat] => 57.159612809986
                            [lng] => 16.370315551758
                            [label] => Kalmar slott
                        )
                )
            [zoom] => 13
            [shape] => line
            [style] => smoothed
            [color] => #c84812
            [markers] =>
        )
    */
    ?>

`label`, `shape`, `style`, `color` and `markers` are all optional. A point that was clicked onto the map has no `label`. A value with no `shape` — which is every value saved before version 1.4.0 — draws no line and no area at all, only the points. `style` is `sharp`, `smoothed` or `bbox` and defaults to `sharp`; `color` defaults to `#999999`; `markers` is only written when it is *off*, so a missing `markers` means the markers should be drawn.

**Rendering a map.** A complete example that draws the markers, unless they are switched off, and whichever shape was chosen, in the chosen colour. It needs your own Google Maps API key:

    <?php
    $value  = get_field('*****FIELD_NAME*****');
    $coords = ( is_array($value) && ! empty($value['coords']) ) ? $value['coords'] : array();

    if ( $coords ) :
        $data = array(
            'coords'  => $coords,
            'zoom'    => isset($value['zoom'])  ? (int) $value['zoom'] : 11,
            'shape'   => isset($value['shape']) ? $value['shape']      : 'none',
            'style'   => isset($value['style']) ? $value['style']      : 'sharp',
            'color'   => isset($value['color']) ? $value['color']      : '#999999',
            // markers are drawn unless the value explicitly says otherwise
            'markers' => ! isset($value['markers']) || $value['markers'],
        );
        ?>
        <div id="my-map" style="height:400px"></div>
        <script src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY"></script>
        <script>
        (function (data) {
            var map = new google.maps.Map(document.getElementById('my-map'), {
                zoom: data.zoom,
                center: { lat: data.coords[0].lat, lng: data.coords[0].lng }
            });

            var path = data.coords.map(function (c) {
                return new google.maps.LatLng(c.lat, c.lng);
            });

            if (data.markers) {
                data.coords.forEach(function (c, i) {
                    new google.maps.Marker({
                        map: map,
                        position: path[i],
                        // label is only there on points added through the search field
                        title: c.label || ('Point ' + (i + 1))
                    });
                });
            }

            // 'shape' is what is drawn: nothing, a line, or a filled area.
            // 'style' is how: straight segments, a smoothed curve, or the
            // bounding box of the points. A value with no shape — every value
            // saved before 1.4.0 — draws the points only.
            var area = data.shape === 'area';

            if (data.shape !== 'none' && path.length > 1) {
                if (data.style === 'bbox') {
                    var bounds = new google.maps.LatLngBounds();
                    path.forEach(function (p) { bounds.extend(p); });
                    new google.maps.Rectangle({ map: map, bounds: bounds,
                        strokeColor: data.color, strokeOpacity: 0.9, strokeWeight: 2,
                        fillColor: data.color, fillOpacity: area ? 0.2 : 0 });
                } else if (area) {
                    new google.maps.Polygon({ map: map,
                        paths: data.style === 'smoothed' ? smooth(path, true) : path,
                        strokeColor: data.color, strokeOpacity: 0.9, strokeWeight: 2,
                        fillColor: data.color, fillOpacity: 0.35 });
                } else {
                    new google.maps.Polyline({ map: map,
                        path: data.style === 'smoothed' ? smooth(path, false) : path,
                        strokeColor: data.color, strokeOpacity: 0.9, strokeWeight: 3 });
                }
            }

            // Google Maps draws straight segments only, so "Smoothed" is a
            // Catmull-Rom curve through the points, emitted as a dense
            // polyline. A closed curve wraps around, so an area has no seam.
            function smooth(points, closed) {
                if (points.length < 3) { return points; }
                var p = closed
                    ? [points[points.length - 1]].concat(points, [points[0], points[1]])
                    : [points[0]].concat(points, [points[points.length - 1]]);
                var out = [];
                for (var i = 1; i + 2 < p.length; i++) {
                    for (var s = 0; s < 16; s++) {
                        var t = s / 16, t2 = t * t, t3 = t2 * t;
                        out.push(new google.maps.LatLng(
                            0.5 * (2 * p[i].lat() + (-p[i-1].lat() + p[i+1].lat()) * t +
                                (2 * p[i-1].lat() - 5 * p[i].lat() + 4 * p[i+1].lat() - p[i+2].lat()) * t2 +
                                (-p[i-1].lat() + 3 * p[i].lat() - 3 * p[i+1].lat() + p[i+2].lat()) * t3),
                            0.5 * (2 * p[i].lng() + (-p[i-1].lng() + p[i+1].lng()) * t +
                                (2 * p[i-1].lng() - 5 * p[i].lng() + 4 * p[i+1].lng() - p[i+2].lng()) * t2 +
                                (-p[i-1].lng() + 3 * p[i].lng() - 3 * p[i+1].lng() + p[i+2].lng()) * t3)
                        ));
                    }
                }
                if (!closed) { out.push(points[points.length - 1]); }
                return out;
            }
        })(<?php echo wp_json_encode($data); ?>);
        </script>
    <?php endif; ?>

**Rendering without a map.** If you only want the coordinates as text, no API key and no JavaScript are needed:

    <?php
    $value = get_field('*****FIELD_NAME*****');

    if ( ! empty($value['coords']) ) {
        echo '<ul class="my-coordinates">';
        foreach ( $value['coords'] as $i => $point ) {
            $label = isset($point['label']) ? $point['label'] : '';
            printf(
                '<li>%s<code>%s, %s</code></li>',
                $label ? '<strong>' . esc_html($label) . '</strong> ' : esc_html( ( $i + 1 ) . '. ' ),
                esc_html($point['lat']),
                esc_html($point['lng'])
            );
        }
        echo '</ul>';
    }
    ?>

Both examples work unchanged on values saved by version 1.0: a missing `label` prints only the coordinates, and a missing `shape` draws the markers on their own.


== Frequently Asked Questions ==

= I am upgrading from 1.1.x. Do I have to change anything? =

No. The field type name, the postmeta key and the JSON format are the same, so your field groups, your saved values and whatever your theme already does with `get_field()` all keep working. What changed is the editing screen, not the data.

= Will the new shapes change how my existing values look? =

No — and that is on purpose. Lines and areas are only drawn when the value actually says so, and a value saved before 1.3.0 says nothing at all: it has no `shape`, no `style`, no `color` and no `markers`. Those fall back to **no shape**, `sharp`, `#999999` and **markers on**, which adds up to exactly what you had before — the numbered points, and nothing drawn between them.

It would have been possible to default old values to an area instead, since the old versions had a "Show area on map" button. That button was only ever an editor preview: pressing it drew a polygon on the edit screen but saved nothing, so there was no way to tell an old value that had it pressed from one that never did. Defaulting to an area would therefore have drawn polygons on sites that never asked for one. If you do want a shape on an older value, open the post, pick Line or Area from the shape menu and update — from then on it is stored with the value.

= How do I get the plugin to show a map on the website? =

By implementing a map on your own. We do not provide a frontend implementation — that is up to you. The Description section has a complete, paste-ready example for both a map and a plain text list.


== Screenshots ==

1. The Coordinates map field in the post editor. Click the map to drop a numbered point and drag a point to move it. The toolbar picks what is drawn through the points — here an Area, Smoothed — its colour, and whether the front end draws the markers with it.
2. "Edit points" opens the list: every point in stored order with its latitude and longitude, the name of any point added through the search field, a drag handle for moving it up or down, and a Remove button per row.
3. Adding the field: pick "Coordinates map" under Content in the ACF field type browser.

== Changelog ==

= 1.4.0 =
**The editor has been rebuilt.** If you are coming from 1.1.1 this is the release where the field stops being a row of buttons over a map and becomes something you can actually work in: points are edited in a list you can drag to reorder, the removal mode is gone, a shape can be drawn through the points in a colour you pick, and clicking a marker tells you what it is. Versions 1.2.0 and 1.3.0 were never released on WordPress.org, so their changes are in this release too and are listed under their own headings below.

**Nothing you have saved changes.** The stored value format is the same JSON in the same postmeta key, `get_field()` returns the same structure, and existing field groups keep working. A value saved before 1.3.0 has no shape keys, so it draws exactly what it drew before: the markers, and nothing else. See "Will the new shapes change how my existing values look?" under Frequently Asked Questions.

* **The point list is where points are edited, and the removal mode is gone.** "Remove points" used to turn on a mode: the map got a red outline and the next marker you clicked was deleted. That mode is removed. "Edit points (N)" opens the list of points instead, and a point is removed with the Remove button on its row. A marker click on the map now always means one thing — show me this point.
* **Points can be reordered by dragging them.** A row in the point list has a handle; drag it and the point moves. The order is what gets stored, what `get_field()` returns and the order the line or area is drawn in, so the markers are renumbered and the map is redrawn as soon as the row is dropped — and a reorder can be undone like any other change. The handle is a real button, so the same move works from the keyboard with the up and down arrow keys. Drag and drop is jQuery UI Sortable, which WordPress already ships; no dependency was added.
* **One button, not two.** "Edit" and "Points (N)" would have been two buttons opening the same list, so they are one: "Edit points (N)".
* **The shape is chosen in two steps.** 1.3.0 had a single menu that mixed the two questions together. The first menu now says what is drawn — nothing, a **Line** or an **Area** — and the second says how: **Sharp** (straight segments), **Smoothed** (a Catmull-Rom curve through every point, drawn as a dense polyline since Google Maps has no curves of its own, and closed for an area) or **Bounding box** (the rectangle the points span). A line is the outline only and an area is filled, so all six combinations mean something. "No shape" is still the default and shows only the markers.
* **The colour picker floats over the map** instead of unfolding inside the toolbar, which used to push the rest of the row sideways every time it was opened.
* **A switch for the markers.** With a shape chosen, "Markers" decides whether the front end draws the points along with the line or the area — turn it off for a clean route or outline. In the editor the markers stay visible, dimmed, because points you cannot see are points you cannot drag, click or reorder.
* **A quieter toolbar.** Undo is an icon at the right end of the row, next to the `i` that folds out what the field can do and the `<>` that reveals the raw stored value. The paragraph of instructions under the toolbar is gone — it said mostly what clicking a map obviously does.
* The front end example in the readme now covers all six shape combinations and respects the marker switch.
* The two field type implementations (ACF 4 and ACF 5/6) rendered two copies of the same markup and had started to drift apart. They now render the same markup from one place.
* The stored value is extended, not changed: a point is `{"lat":…,"lng":…}` exactly as before, with an optional `"label"`, and the value carries an optional `"shape"`, `"style"`, `"color"` and `"markers"` only when a shape is chosen. A value saved by an earlier version was loaded, saved and read back byte for byte identical, and `get_field()` returns the same structure it always has.

= 1.3.0 =
Never released on WordPress.org on its own — it ships as part of 1.4.0. Listed separately so the jump from 1.1.1 is readable.

* **Click a point to see what it is.** A marker click opens a popup with the point's number and its latitude and longitude. Until now the only way to read a point's coordinates was to open the list and count rows. Nothing is looked up when you click; the popup only shows what is already stored.
* **Searched places keep their name.** The search field used to only pan the map. It now drops a point where the place was found and stores the name you searched for, which the popup and the point list show. Nothing extra is asked of Google: the name is the text you typed, and the lookup is the one the search already made. Clicking the map still adds a plain point with no name, since naming those would mean a lookup per click.
* **A shape can be drawn through the points.** The old "Show area on map" was a button in the editor that drew a polygon but never saved anything, so the front end had no way of knowing you had pressed it. The shape is now part of the stored value, and it can be a line as well as an area. (1.4.0 splits the choice into the two menus described above.)
* **The shape has a colour**, picked with WordPress's own `wp-color-picker` — no new dependency. The map redraws in that colour as you pick it.
* **The readme got front end examples worth pasting.** The plugin renders nothing on the front end itself — `[acf field="…"]` returns an empty string for this field type — so the readme carries a complete map example and a map-free example that lists the points as text. Both handle values saved by 1.0.x, which have none of the new keys.
* The point list shows the name under the coordinates for points that have one.

= 1.2.0 =
Never released on WordPress.org on its own — it ships as part of 1.4.0.

* **Points can be removed one at a time from the coordinates list.** Every row in "Show coordinates" now has its own Remove button, so a single point can be deleted without turning on a mode and without hunting for the right marker among overlapping ones.
* **Undo.** A new "Undo" button takes back the last change — a point added by a stray click, a point removed by mistake, or a marker dragged to the wrong place. Up to 30 steps are kept for the lifetime of the page.
* **Removal mode looks like a mode.** It used to be signalled by nothing but a blue tint moving between two buttons. The button now reads "Cancel removing" while the mode is on and reports itself as pressed, the map gets a red outline, a notice explains what a click will do, and Esc leaves the mode.
* **The order of the points is visible.** Markers are numbered, and the numbers are reassigned when a point is removed. That order is what gets stored, what `get_field()` returns and what the "Show area on map" polygon follows — it was previously impossible to see.
* **"Add coordinate" became "Add point" and now actually adds a point.** Before, the button only switched removal mode back off, which is not what its label suggested. It is also the only way to add a point without a mouse.
* Buttons are real buttons. They were `<input type="submit">`, so a click submitted the post form if the map JavaScript had not loaded.
* Screen reader support: the toolbar buttons expose their pressed/expanded state, the search field has a label, and adding, moving, removing and undoing are announced through a live region.
* "Copy" now really selects the value it reveals — the old code called jQuery's `.select()` event shorthand, which only fired an event.
* Fixed a listener leak: marker click and drag handlers were attached outside the guard in `AddMarker()`, so every map click made while in removal mode piled another copy of them onto the previous marker.
* **Fixed the field type label alignment properly.** 1.1.1 worked around it by shortening the label. The cause is that ACF centres its field type cards with flexbox but never sets `text-align` on the label, so any label that wraps to two lines renders left aligned — the built-in oEmbed card does exactly the same with a long enough label. The plugin now supplies the missing `text-align` on the field type browser only, so the length of the label no longer matters.
* The field type name (`multiple-coordinates-field`) and the stored value format (`{"coords":[{lat,lng},…],"zoom":N}`) are unchanged, so existing field groups and existing values keep working.

= 1.1.1 =
* The field type is now called **Coordinates map** in ACF's field type browser instead of "Multiple coordinates map". ACF gives every field type card a fixed width and only centres labels that fit on one line, so the old label wrapped and rendered left aligned next to the centred built-in field types. Only the displayed label changed — the internal field type name (`multiple-coordinates-field`) is untouched, so existing field groups and stored values keep working.

= 1.1.0 =
* Corrected the plugin description: it claimed markers were removed by right-clicking them, which the plugin has never done. Removal works the way the Usage section describes — press "Remove coordinate", then click the marker you want gone.
* Verified on WordPress 7.1 with ACF 6.8: the field renders, the map and its markers draw, click adds a marker, dragging moves it, "Remove coordinate" deletes it, and the value survives save/reload unchanged. Tested up to raised to 7.1.
* **Fixed: the field type did not exist on ACF 5 and ACF 6.** The plugin only hooked `acf/register_fields`, which is the ACF 4 API and was removed from ACF years ago. On any modern ACF the field type was never registered, so "Multiple coordinates map" did not show up in the field type list and existing fields rendered nothing. The field type is now registered through `acf/include_field_types` as well, and the field type implementation has been ported to the ACF 5/6 field API (`initialize()`, `render_field()`, `format_value()`, `update_value()`). The ACF 4 implementation is kept for anyone still running ACF 4.
* **Fixed: the JavaScript never ran on ACF 5/6.** Initialization used `$(document).live('acf/setup_fields')` — `.live()` was removed in jQuery 1.9. The map now initializes on ACF's `ready_field` / `append_field` actions (so it also works for fields added dynamically), with a fallback for older ACF versions.
* Fixed the asset URLs: they were built with the ACF 4 helper filters `acf/helpers/get_dir` / `acf/helpers/get_path`, which no longer exist, so the CSS and JS pointed nowhere on ACF 5/6.
* Fixed the zoom level not being stored when the map had no markers, and the zoom level is now saved when you zoom the map.
* Fixed a JavaScript error in "Show area on map" (an undefined variable was passed to the polygon).
* Field values are unchanged: still the same JSON structure in the same postmeta key, so values saved with earlier versions keep working, and a malformed value is never overwritten.
* The Google Maps API key filter is renamed from `acf_multi_coords_gmaps_key` to `acfmc_gmaps_key` (shorter, correctly prefixed). The old name could never fire in practice, since the field type was not registered on ACF 5/6 at all.

= 1.0.4 =
* Google Maps API key — read from ACF's global `google_api_key` setting (`acf_get_setting`) and a `acf_multi_coords_gmaps_key` filter, so existing ACF users get a working map out of the box. Without a key, Google Maps falls back to limited mode (watermark).
* Cleaned up leftover `console.log` calls in `js/map.js`.
* Tested up to rolled back to WordPress 7.0: the field type has not been verified working on 7.1.

= 1.0.3 =
* Plugin Check compliance: added `License`/`License URI` headers, `ABSPATH` direct-access guard, replaced `<?= ?>` short echo tags with `<?php echo esc_attr() ?>`, added explicit version arguments to `wp_register_style()`/`wp_register_script()`, switched protocol-relative Google Maps URL to `https`.
* Updated Author URI to https://jonashjalmarsson.se.
* Trimmed readme tags to 5.
* Tested up to WordPress 6.9.

= 1.0.2 =
Copy feature added to copy points from one Multiple Coordinates value to another.

1. Press the Copy button, right click on the selection and select Copy.
2. Go to the destination Multiple Coordinates value and press the Copy button, then right click and select Paste.
3. Publish or update the page or post to apply the new points.

= 1.0.1 =

* Bugfix 1. Get lat and lng from Marker via correct API function.
* Bugfix 2. "Show area on map" button toggle now works.
* Optimized init of map when many markers.

= 1.0.0 =

* First release.


== Upgrade Notice ==

= 1.4.0 =
The editor is rebuilt: points are edited and reordered in a drag-and-drop list, the removal mode is gone, a line or an area can be drawn through the points in a colour you pick, and clicking a marker shows what it is. Your saved values are untouched — the format is unchanged and old values still draw only their markers.
