can someone help me? I am trying get width and height of rectangle the google maps api.
I'm doing it this way:
google.maps.event.addListener(drawingManager, 'rectanglecomplete', function (rectangle) {
//height
var height= google.maps.geometry.spherical.computeDistanceBetween(
new google.maps.LatLng(rectangle.bounds.toJSON().north, rectangle.bounds.toJSON().east),
new google.maps.LatLng(rectangle.bounds.toJSON().south, rectangle.bounds.toJSON().west)
);
//width
var width= google.maps.geometry.spherical.computeDistanceBetween(
new google.maps.LatLng(rectangle.bounds.toJSON().north, rectangle.bounds.toJSON().east),
new google.maps.LatLng(rectangle.bounds.toJSON().north, rectangle.bounds.toJSON().west)
);
});
You are calculating the wrong distance for the height (NorthEast corner to SouthWest corner). This:
//height
var height= google.maps.geometry.spherical.computeDistanceBetween(
new google.maps.LatLng(rectangle.bounds.toJSON().north, rectangle.bounds.toJSON().east),
new google.maps.LatLng(rectangle.bounds.toJSON().south, rectangle.bounds.toJSON().west)
);
Should be:
//height
var height= google.maps.geometry.spherical.computeDistanceBetween(
new google.maps.LatLng(rectangle.bounds.toJSON().north, rectangle.bounds.toJSON().east),
new google.maps.LatLng(rectangle.bounds.toJSON().south, rectangle.bounds.toJSON().east)
);
(or you could make both longitudes .west)
Note that the width will be different at the northern and southern edges if it appears as a rectangle in the map projection.
proof of concept fiddle
code snippet:
// This example requires the Drawing library. Include the libraries=drawing
// parameter when you first load the API. For example:
// <script src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&libraries=drawing">
function initMap() {
var map = new google.maps.Map(document.getElementById('map'), {
center: {
lat: -34.397,
lng: 150.644
},
zoom: 8
});
var rectBounds = new google.maps.LatLngBounds();
var NW = map.getCenter();
var markerNW = new google.maps.Marker({
map: map,
position: NW,
label: "C",
title: NW.toUrlValue(6)
})
rectBounds.extend(map.getCenter());
var NE = google.maps.geometry.spherical.computeOffset(map.getCenter(), 100, 90);
var markerNE = new google.maps.Marker({
map: map,
position: NE,
label: "NE",
title: NE.toUrlValue(6)
});
rectBounds.extend(markerNE.getPosition());
var SW = google.maps.geometry.spherical.computeOffset(map.getCenter(), 100, 180);
var markerSW = new google.maps.Marker({
map: map,
position: SW,
label: "SW",
title: SW.toUrlValue(6)
})
rectBounds.extend(markerSW.getPosition());
var SE = google.maps.geometry.spherical.computeOffset(NE, 100, 180);
var markerSE = new google.maps.Marker({
map: map,
position: SE,
label: "SE",
title: SE.toUrlValue(6)
})
rectBounds.extend(markerSE.getPosition());
var rect = new google.maps.Rectangle({
map: map,
bounds: rectBounds
})
map.fitBounds(rectBounds);
var drawingManager = new google.maps.drawing.DrawingManager({
drawingMode: google.maps.drawing.OverlayType.RECTANGLE,
drawingControl: true,
drawingControlOptions: {
position: google.maps.ControlPosition.TOP_CENTER,
drawingModes: ['rectangle']
},
markerOptions: {
icon: 'https://developers.google.com/maps/documentation/javascript/examples/full/images/beachflag.png'
},
circleOptions: {
fillColor: '#ffff00',
fillOpacity: 1,
strokeWeight: 5,
clickable: false,
editable: true,
zIndex: 1
}
});
drawingManager.setMap(map);
google.maps.event.addListener(drawingManager, 'rectanglecomplete', function(rectangle) {
//height
var height = google.maps.geometry.spherical.computeDistanceBetween(
new google.maps.LatLng(rectangle.bounds.toJSON().north, rectangle.bounds.toJSON().east),
new google.maps.LatLng(rectangle.bounds.toJSON().south, rectangle.bounds.toJSON().east)
);
//width
var width = google.maps.geometry.spherical.computeDistanceBetween(
new google.maps.LatLng(rectangle.bounds.toJSON().north, rectangle.bounds.toJSON().east),
new google.maps.LatLng(rectangle.bounds.toJSON().north, rectangle.bounds.toJSON().west)
);
document.getElementById('dimensions').innerHTML = "height:" + height.toFixed(2) + " m<br>width:" + width.toFixed(2) + " m";
});
}
/* Always set the map height explicitly to define the size of the div
* element that contains the map. */
#map {
height: 100%;
}
/* Optional: Makes the sample page fill the window. */
html,
body {
height: 100%;
margin: 0;
padding: 0;
}
<div id="dimensions"></div>
<div id="map"></div>
<!-- Replace the value of the key parameter with your own API key. -->
<script src="https://maps.googleapis.com/maps/api/js?key=AIzaSyCkUOdZ5y7hMm0yrcCQoCvLwzdM6M8s5qk&libraries=drawing,geometry&callback=initMap" async defer></script>
I managed to solve it like this:
// get the coordinates for the NE and SW corners
NE = rectangle.bounds.getNorthEast ();
SW = rectangle.bounds.getSouthWest ();
// from that, figure out the latitudes and the longitudes
lat1 = NE.lat ();
lat2 = SW.lat ();
lng1 = NE.lng ();
lng2 = SW.lng ();
// construct new LatLngs using the coordinates for the horizontal distance between lng1 and lng2
horizontalLatLng1 = new google.maps.LatLng (lat1, lng1);
horizontalLatLng2 = new google.maps.LatLng (lat1, lng2);
// construct new LatLngs using the coordinates for the vertical distance between lat1 and lat2
verticalLatLng1 = new google.maps.LatLng (lat1, lng1);
verticalLatLng2 = new google.maps.LatLng (lat2, lng1);
// work out the distance horizontally
width = google.maps.geometry.spherical.computeDistanceBetween (horizontalLatLng1, horizontalLatLng2);
// work out the distance vertically
height = google.maps.geometry.spherical.computeDistanceBetween (verticalLatLng1, verticalLatLng2);
https://duncan99.wordpress.com/2013/10/19/google-maps-size/
Related
I have got rectangle bounds, used
map.getBounds();
Now I would like to calculate distance (in meters) between rectangle center and shorter middle point.
Already have some code:
var mapCenter = this.map.getCenter()
var mapBounds = this.map.getBounds();
var southEast = mapBounds.getSouthWest();
var middlePoint = ??;
var radius = google.maps.geometry.spherical.computeDistanceBetween(mapCenter, middlePoint);
But I don't know how to get shorter distance (from center) middle point.
The latitude of the "top" of the bounds is the northern latitude of the bounds: mapBounds.getNorthEast().lat(). The longitude of the center of the bounds is the longitude of the center of the bounds mapBounds.getCenter().lng()
(if you don't know which side is the "shorter" one, calculate both and use the shorter value)
var mapCenter = map.getCenter()
var mapBounds = map.getBounds();
var southEast = mapBounds.getSouthWest();
var middlePoint = new google.maps.LatLng(mapBounds.getNorthEast().lat(), mapBounds.getCenter().lng());
var radius = google.maps.geometry.spherical.computeDistanceBetween(mapCenter, middlePoint);
proof of concept fiddle
code snippet:
function initialize() {
var map = new google.maps.Map(
document.getElementById("map_canvas"), {
center: new google.maps.LatLng(37.4419, -122.1419),
zoom: 13,
mapTypeId: google.maps.MapTypeId.ROADMAP
});
google.maps.event.addListenerOnce(map, 'bounds_changed', function() {
var mapCenter = map.getCenter()
var mapBounds = map.getBounds();
var rectangle = new google.maps.Rectangle({
map: map,
bounds: mapBounds
});
var southEast = mapBounds.getSouthWest();
var middlePoint = new google.maps.LatLng(mapBounds.getNorthEast().lat(), mapBounds.getCenter().lng());
var radius = google.maps.geometry.spherical.computeDistanceBetween(mapCenter, middlePoint);
var circle = new google.maps.Circle({
center: mapCenter,
radius: radius,
map: map
});
var marker = new google.maps.Marker({
position: middlePoint,
map: map
});
var line = new google.maps.Polyline({
map: map,
path: [mapCenter, middlePoint]
});
document.getElementById('info').innerHTML = radius.toFixed(2);
map.setZoom(12);
});
}
google.maps.event.addDomListener(window, "load", initialize);
html,
body,
#map_canvas {
height: 100%;
width: 100%;
margin: 0px;
padding: 0px
}
<script src="https://maps.googleapis.com/maps/api/js?libraries=geometry"></script>
<div id="info"></div>
<div id="map_canvas"></div>
My goal is when they have a drawing tool like the rectangle tool, if they single click somewhere it centers the map where they clicked as to make it easier for them to draw their rectangle without needing to switch tools or drag to the window edge and have it scroll.
I cannot use Google Map's events because they disable them if you use their DrawingManager, so I'm forced to use jQuery or vanilla JS.
I have a map that is 100% wide and ~50% of the window height. Here's the basic code I've been trying everything I can think of on:
$('#myMapWapperElement').on('click', function (event) {
var position = settings.map.getProjection().fromPointToLatLng(
new google.maps.Point(
event.offsetX,
event.offsetY
)
);
map.setCenter(position);
});
This, however, makes the map jump all over when I click. It doesn't center where the marker clicked at all. I tried offsetX, pageX, and also relative by offsetX - "elementPositionX". Nothing seems to center correctly.
The fromPointToLatLng and fromLatLngToPoint methods of google.maps.Projection object translate between points in a 256x256 coordinate system and real world coordinates. You could utilize the following function to translate from pixel coordinates on a map projection to LatLng values:
var fromPixelToLatLng = function (map, x, y) {
var projection = map.getProjection();
var topRight = projection.fromLatLngToPoint(map.getBounds().getNorthEast());
var bottomLeft = projection.fromLatLngToPoint(map.getBounds().getSouthWest());
var scale = 1 << map.getZoom();
return projection.fromPointToLatLng(new google.maps.Point(x / scale + bottomLeft.x, y / scale + topRight.y));
};
Example
function initMap() {
var map = new google.maps.Map(document.getElementById('map'), {
center: { lat: -34.397, lng: 150.644 },
zoom: 8
});
var infowindow = new google.maps.InfoWindow({
content: ''
});
$("#map").on('click', function (event) {
var position = fromPixelToLatLng(map, event.offsetX, event.offsetY);
infowindow.setContent('Pixel(' + event.offsetX + ',' + event.offsetY + ') -> LatLng:' + position.toString());
infowindow.setPosition(position);
infowindow.open(map);
map.setCenter(position);
});
//var drawingManager = new google.maps.drawing.DrawingManager({
// drawingMode: google.maps.drawing.OverlayType.MARKER,
// drawingControl: true,
// drawingControlOptions: {
// position: google.maps.ControlPosition.TOP_CENTER,
// drawingModes: [
// google.maps.drawing.OverlayType.MARKER,
// google.maps.drawing.OverlayType.CIRCLE,
// google.maps.drawing.OverlayType.POLYGON,
// google.maps.drawing.OverlayType.POLYLINE,
// google.maps.drawing.OverlayType.RECTANGLE
// ]
// }
//});
//drawingManager.setMap(map);
}
var fromPixelToLatLng = function (map, x, y) {
var projection = map.getProjection();
var topRight = projection.fromLatLngToPoint(map.getBounds().getNorthEast());
var bottomLeft = projection.fromLatLngToPoint(map.getBounds().getSouthWest());
var scale = 1 << map.getZoom();
return projection.fromPointToLatLng(new google.maps.Point(x / scale + bottomLeft.x, y / scale + topRight.y));
};
html, body {
height: 100%;
margin: 0;
padding: 0;
}
#map {
height: 100%;
}
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
<div id="map"></div>
<script src="https://maps.googleapis.com/maps/api/js?libraries=drawing&callback=initMap"
async defer></script>
You need to use the .fromContainerPixelToLatLng of MapCanvasProjection
That method is available on the OverlayView class.
proof of concept fiddle
code snippet:
var geocoder;
var map;
function initialize() {
map = new google.maps.Map(
document.getElementById("map_canvas"), {
center: new google.maps.LatLng(37.4419, -122.1419),
zoom: 13,
mapTypeId: google.maps.MapTypeId.ROADMAP
});
overlay = new google.maps.OverlayView();
overlay.draw = function() {
console.log("draw");
};
overlay.setMap(map);
google.maps.event.addListener(overlay, 'projection_changed', function() {
var projection = overlay.getProjection();
$('#map_canvas').on('click', function(event) {
var position = projection.fromContainerPixelToLatLng(
new google.maps.Point(
event.offsetX,
event.offsetY
)
);
map.setCenter(position);
});
})
}
google.maps.event.addDomListener(window, "load", initialize);
html,
body,
#map_canvas {
height: 100%;
width: 100%;
margin: 0px;
padding: 0px
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://maps.googleapis.com/maps/api/js"></script>
<div id="map_canvas"></div>
I have implemented Google Maps API V3 on a page and for some reason the position of the mouse is off.
In the image, my mouse is in the red circle but the map thinks it's where the blue circle is.
This issue also affects the dragable points when using the directions service.
I build the map using the following:
var center = new google.maps.LatLng(53.42263, -7.9541);
var latlngbounds = new google.maps.LatLngBounds( );
var map = new google.maps.Map(document.getElementById('map_canvas'), {
center: new google.maps.LatLng( 53.42263, -7.9541 ),
zoom: 7,
mapTypeId: google.maps.MapTypeId.ROADMAP
});
var clusters = [];
var oms = new OverlappingMarkerSpiderfier(map);
var iw = new google.maps.InfoWindow();
var spiderIcon = new google.maps.MarkerImage (
'http://www.irishcottageholidays.com/images/cottage1.png',
new google.maps.Size(32,32),
new google.maps.Point(0,0),
new google.maps.Point(16,20)
)
oms.addListener('click', function(marker, event) {
load_content(map, marker, iw, marker.id)
});
oms.addListener('spiderfy', function(markers) {
for(var i = 0; i < markers.length; i ++) {
markers[i].setIcon(spiderIcon);
}
iw.close();
});
for (var i = 0; i < data.locations.length; i++) {
var dataPhoto = data.locations[i];
var latLng = new google.maps.LatLng(dataPhoto.latitude,dataPhoto.longitude);
latlngbounds.extend( latLng );
var marker = new google.maps.Marker({
position: latLng,
icon: 'http://www.irishcottageholidays.com/images/cottage1.png',
map: map,
id: dataPhoto.mID,
title: dataPhoto.name
});
clusters.push(marker);
oms.addMarker(marker);
}
var mcStyles = [
{
textColor: 'deeppink',
textSize: 18,
anchor: [17,0],
url: '/assets/cms/images/cottage_cluster.png',
height: 50,
width: 50
}
]
var mcOptions = {
styles: mcStyles,
gridSize: 5,
maxZoom: 15
}
var markerCluster = new MarkerClusterer(map, clusters, mcOptions);
map.fitBounds( latlngbounds );
There is an issue with Firefox 39.0 and v=3.exp.
issue 8278 in the issue tracker
How can I create a draggable rectangle in map api v3 as you see in my code my rectangle is draggable onclick on the marker center i wont to drag my rectangle on click of all the rectangle thx for help :
(function()
{
window.onload = function()
{
var path;
var counter = 0;
// Creating a map
var options =
{
zoom: 11,
center: new google.maps.LatLng(49.2541, -123.072),
mapTypeId: google.maps.MapTypeId.ROADMAP
};
map = new google.maps.Map(document.getElementById('mapDiv'), options);
};// end window.onload
})();// end anonymous function
//-------------------------createRectangle BEGINS---------------------------
/**
* Creates the rectangle
*/
function createRectangle()
{
// make the center marker
try
{
markerCenter.setMap(null);
markerSouthWest.setMap(null);
markerNorthEast.setMap(null);
rectangle.setMap(null);
fusionLayer.setMap(null);
}
catch(err){}
centerPositionSave = map.getCenter();
latLngSouthWest = map.getCenter().DestinationPoint(225,4200); // 225 degrees, 1200 meters
latLngNorthEast = map.getCenter().DestinationPoint(45,4200); // 45 degrees, 1200 meters
bounds = new google.maps.LatLngBounds(latLngSouthWest,latLngNorthEast);
rectangle = new google.maps.Rectangle
(
{
strokeWeight: 2,
bounds:bounds,
map:map,
}
); // end rectangle
markerSouthWest = new google.maps.Marker(
{
draggable: true,
title: 'south west',
icon:polyEditSquare,
raiseOnDrag:false,
position: latLngSouthWest,
map: map
}); // end markerSouthWest
google.maps.event.addListener(markerSouthWest,'drag',markerSouthWestDrag);
markerNorthEast = new google.maps.Marker(
{
draggable: true,
title: 'north east',
icon:polyEditSquare,
raiseOnDrag:false,
position: latLngNorthEast,
map: map
}); // end markerNorthEast
google.maps.event.addListener(markerNorthEast,'drag',markerNorthEastDrag);
markerCenter = new google.maps.Marker(
{
draggable: true,
title: 'center',
icon: new google.maps.MarkerImage("icons/move.png"),
raiseOnDrag:false,
position: map.getCenter(),
map: map
});// end markerCenter
markerClose = new google.maps.Marker(
{
draggable: false,
title: 'Fermer',
icon: new google.maps.MarkerImage("icons/x.png", new google.maps.Size(16,16), new google.maps.Point(0,0), new google.maps.Point(8,8)),
raiseOnDrag:false,
position: new google.maps.LatLng(latLngNorthEast.lat(), latLngSouthWest.lng()),
map: map
});// end markerClose
google.maps.event.addListener(markerCenter, 'drag', markerCenterDrag);
google.maps.event.addListener(markerClose,'click',markerCenterDoubleClick);
}//end of createRectangle
//new google.maps.LatLng(latLngSouthWest.lat(),latLngNorthEast.lng())///////////////////////////::::::
//------------------------------createRectangle ENDS------------------------
//-------------------------markerCenterDoubleClick BEGINS---------------------------
/**
* Handles the markerCenter doubleclick event. Removes the rectangle.
*/
function markerCenterDoubleClick(e)
{
rectangle.setMap(null);
markerCenter.setMap(null);
markerSouthWest.setMap(null);
markerNorthEast.setMap(null);
markerClose.setMap(null);
}//end of markerCenterDoubleClick
//------------------------------markerCenterDoubleClick ENDS------------------------
//-------------------------markerCenterDrag BEGINS---------------------------
/**
* Handles the center marker drag event. We want the southwest and northwest markers to update accordingly
*/
function markerCenterDrag(e)
{
var southWest = markerSouthWest.getPosition();
var northEast = markerNorthEast.getPosition();
centerPositionNew = markerCenter.getPosition();
var distance = google.maps.geometry.spherical.computeDistanceBetween(centerPositionSave,centerPositionNew);
var headingNew = google.maps.geometry.spherical.computeHeading(centerPositionSave,centerPositionNew);
var newSouthWest = google.maps.geometry.spherical.computeOffset(southWest,distance,headingNew);
var newNorthEast = google.maps.geometry.spherical.computeOffset(northEast,distance,headingNew);
markerSouthWest.setPosition(newSouthWest);
markerNorthEast.setPosition(newNorthEast);
bounds = new google.maps.LatLngBounds(newSouthWest,newNorthEast);
rectangle.setBounds(bounds);
centerPositionSave = centerPositionNew;
markerClose.setPosition(new google.maps.LatLng(newNorthEast.lat(), newSouthWest.lng()));
}//end of markerCenterDrag
//------------------------------markerCenterDrag ENDS------------------------
//-------------------------markerSouthWestDrag BEGINS---------------------------
/**
* Handles the southwest marker drag event. We want the rectangle to update accordingly.
*/
function markerSouthWestDrag(e)
{
latLngSouthWest = markerSouthWest.getPosition();
latLngNorthEast = markerNorthEast.getPosition();
bounds = new google.maps.LatLngBounds(latLngSouthWest,latLngNorthEast);
rectangle.setBounds(bounds);
center = bounds.getCenter();
markerCenter.setPosition(center);
centerPositionSave = center;
markerClose.setPosition(new google.maps.LatLng(latLngNorthEast.lat(), latLngSouthWest.lng()));
}//end of markerSouthWestDrag
//------------------------------markerNorthEastDrag ENDS------------------------
/**
* Handles the southwest marker drag event. We want the rectangle to update accordingly.
*/
function markerNorthEastDrag(e)
{
latLngSouthWest = markerSouthWest.getPosition();
latLngNorthEast = markerNorthEast.getPosition();
bounds = new google.maps.LatLngBounds(latLngSouthWest,latLngNorthEast);
rectangle.setBounds(bounds);
center = bounds.getCenter();
markerCenter.setPosition(center);
centerPositionSave = center;
markerClose.setPosition(new google.maps.LatLng(latLngNorthEast.lat(), latLngSouthWest.lng()));
}//end of markerNorthEastDrag
//------------------------------markerNorthEastDrag ENDS------------------------
//-------------------------fusionCommunityCentres BEGINS---------------------------
/**
* Puts the community centres Fusion Table on the map
*/
function fusionCommunityCentres()
{
tableId = 1067437;
southWest = markerSouthWest.getPosition().toString();
northEast = markerNorthEast.getPosition().toString();
query = "SELECT * FROM " + tableId + " WHERE ST_INTERSECTS(geometry, RECTANGLE(LATLNG" +
southWest + ", LATLNG" + northEast + "))";
$("#queryOutput").html("Query sent to Fusion Tables:<br>" + query);
fusionLayer = new google.maps.FusionTablesLayer(tableId,
{
query: query,
map:map
});
//layer.setMap(map);
}//end of fusionCommunityCentres
//------------------------------fusionCommunityCentres ENDS------------------------
Here I drag a single rectangle.
Since there's no drag event for Rectangles, I assign a marker to its center, and let its drag event control the rect movement.
The code can be extended, like adding a marker directly to the Rectangle object, or even subclass it. You decide.
You can set in oprtions rectangle to be draggable.
Try:
var rectangle = new google.maps.Rectangle({
strokeColor: '#FF0000',
strokeOpacity: 0.8,
strokeWeight: 2,
fillColor: '#FF0000',
fillOpacity: 0.35,
map: map,
draggable:true,//<-----------Here set draggable option
bounds: new google.maps.LatLngBounds(
new google.maps.LatLng(33.671068, -116.25128),
new google.maps.LatLng(33.785282, -116.133942))
});
DEMO
I would like to display a text label next to the markers on google maps. I've used Virtual Earth before and I'm just starting to use Google Maps. I tried setting the Title property but that only changes the roll over text.
Is there a way to display a small line of text underneath a marker that will stay there as the user zooms, pans and uses the map?
If you just want to show label below the marker, then you can extend google maps Marker to add a setter method for label and you can define the label object by extending google maps overlayView like this..
Demo: jsFiddle
<script type="text/javascript">
var point = { lat: 22.5667, lng: 88.3667 };
var markerSize = { x: 22, y: 40 };
google.maps.Marker.prototype.setLabel = function(label){
this.label = new MarkerLabel({
map: this.map,
marker: this,
text: label
});
this.label.bindTo('position', this, 'position');
};
var MarkerLabel = function(options) {
this.setValues(options);
this.span = document.createElement('span');
this.span.className = 'map-marker-label';
};
MarkerLabel.prototype = $.extend(new google.maps.OverlayView(), {
onAdd: function() {
this.getPanes().overlayImage.appendChild(this.span);
var self = this;
this.listeners = [
google.maps.event.addListener(this, 'position_changed', function() { self.draw(); })];
},
draw: function() {
var text = String(this.get('text'));
var position = this.getProjection().fromLatLngToDivPixel(this.get('position'));
this.span.innerHTML = text;
this.span.style.left = (position.x - (markerSize.x / 2)) - (text.length * 3) + 10 + 'px';
this.span.style.top = (position.y - markerSize.y + 40) + 'px';
}
});
function initialize(){
var myLatLng = new google.maps.LatLng(point.lat, point.lng);
var gmap = new google.maps.Map(document.getElementById('map_canvas'), {
zoom: 5,
center: myLatLng,
mapTypeId: google.maps.MapTypeId.ROADMAP
});
var myMarker = new google.maps.Marker({
map: gmap,
position: myLatLng,
label: 'Hello World!',
draggable: true
});
}
</script>
<style>
.map-marker-label{
position: absolute;
color: blue;
font-size: 16px;
font-weight: bold;
}
</style>
This will work.