Turns out my code was pretty much working but there was another issue in there that was stopping the map marker updating. I've since resolved it.
The original form is huge so here's a cut down snippet of the part that does the marker update in case anyone needs it. This cut-down version isn't tested but should be good.
Incidentally, the "parent element ID" to which I referred is the id of the entry in which your map resides.
Basic location update form (my entry is 'property')
<form id="property-edit__form" method="post" accept-charset="UTF-8" enctype="multipart/form-data">
{{ getCsrfInput() }}
{% if property is defined %}
<input type="hidden" name="action" value="entries/saveEntry">
<input type="hidden" name="entryId" value="{{ property.id }}">
<input type="hidden" name="sectionId" value="{{ property.sectionId }}">
<input type="hidden" name="enabled" value="{{ property.enabled }}">
{% else %}
<input type="hidden" name="action" value="entries/saveEntry">
<input type="hidden" name="sectionId" value="3">
<input type="hidden" name="enabled" value="0">
<input type="hidden" name="redirect" value="me/property/saved">
{% endif %}
<div class="property-edit__map-fields">
<label>
Street Address
<input class="js-address-part" type="text" name="fields[location][street1]" value="{{property.location.street1 ?? ''}}">
</label>
<label>
City
<input class="js-address-part" type="text" name="fields[location][city]" value="{{property.location.city ?? ''}}">
</label>
<label>
County
<input class="js-address-part" type="text" name="fields[location][state]" value="{{property.location.state ?? ''}}">
</label>
<label>
Post Code
<input class="js-address-part" type="text" name="fields[location][zip]" value="{{property.location.zip ?? ''}}">
</label>
<label>
Country
<input class="js-address-part" type="text" name="fields[location][country]" value="{{property.location.country ?? ''}}">
</label>
<input class="js-address-lat" type="hidden" name="fields[location][lat]" value="{{property.location.lat ?? ''}}">
<input class="js-address-lng" type="hidden" name="fields[location][lng]" value="{{property.location.lng ?? ''}}">
<div><button class="js-address-lookup">Lookup address</button></div>
</div>
<div class="property-edit__map">
{% if property is defined and property.location.hasCoords %}
{% set options = {
height: 300,
width: '100%',
zoom: 11,
markerOptions: {
icon: '/images/map-marker.png'
},
}
%}
{% else %}
{% set options = {
height: 300,
width: '100%',
zoom: 4,
center: { 'lat': 51.499998 , 'lng': -0.1166662},
markerOptions: {
icon: '/images/map-marker.png'
},
}
%}
{% endif %}
{% if property is defined %}
{{ craft.smartMap.map(property.location, options) }}
{% endif %}
</div>
<input type="submit" value="Save">
</form>
And some Javascript to do the business (using jQuery here).
<script>
$('body').on('click', '.js-address-lookup', function (event) {
event.preventDefault;
var target = '';
$('.js-address-part').each(function(){
target += $(this).val() + ',';
});
var data = {target: target};
var csrfTokenName = "{{ craft.config.get('csrfTokenName') }}";
var csrfTokenValue = "{{ craft.request.getCsrfToken }}";
data[csrfTokenName] = csrfTokenValue; // Append CSRF Token to outgoing data
$.post('/actions/smartMap/lookup', data, function(response) {
if(response.status = 'OK'){
// get lat/lng and populate hidden fields
$('.js-address-lat').val(response.results[0].geometry.location.lat);
$('.js-address-lng').val(response.results[0].geometry.location.lng);
// update map (add marker)
var lat = parseFloat(response.results[0].geometry.location.lat);
var lng = parseFloat(response.results[0].geometry.location.lng);
var newLatLng = new google.maps.LatLng(lat, lng);
{% if property is defined %}
smartMap.marker['smartmap-mapcanvas-1.{{ property.id }}.location'].setPosition(newLatLng)
{% endif %}
//smartMap.map['smartmap-mapcanvas-1'].setOptions({styles: styles});
smartMap.map['smartmap-mapcanvas-1'].panTo(newLatLng);
smartMap.refreshMap(smartmap-mapcanvas-1);
} else {
alert('No address could be found');
}
});
});
</script>