HiveBrain v1.2.0
Get Started
← Back to all entries
patternpythonMinor

Coordinates of 2D points zoomed in/out

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
pointszoomedoutcoordinates

Problem

This might be a simple issue that I am overcomplicating, but I've spent quite some time reading about polygon scaling and I've come to the conclusion that it is not precisely what I need.

Given a set of (x, y) coordinates for N points (shown in blue) I need the new set of coordinates that result after zooming in/out a given scale factor (shown in red).

I've come up with the simple method shown below, but I wonder if there might be another approach and/or more reasonable zooming methods.

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.patches as patches

N = 5
xy = [np.random.uniform(0., 1000., 2) for _ in range(N)]
x, y = zip(*xy)

# Center of xy points, defined as the center of the minimal rectangle that
# contains all points.
xy_center = ((min(x) + max(x)) * .5, (min(y) + max(y)) * .5)

# Difference between the center coordinates and the xy points.
delta_x, delta_y = xy_center[0] - x, xy_center[1] - y

# Zoom scale (0. < scale)
scale = 1.5

# Scaled xy points.
x_scale = xy_center[0] - scale * delta_x
y_scale = xy_center[1] - scale * delta_y

ax = plt.subplot(111)
# Original xy points.
ax.scatter(x, y, c='b')
# Defined center.
ax.scatter(*xy_center, marker='x', c='g')
# Zoomed points.
ax.scatter(x_scale, y_scale, c='r')
# Square: bottom left corner, width, height
ax.add_patch(
    patches.Rectangle(
        (min(x), min(y)), (max(x) - min(x)), (max(y) - min(y)), fill=False))
plt.show()

Solution

When working with NumPy, it's best to keep all our data in the form of NumPy arrays, instead of converting back and forth between NumPy and Python data structures.

-
So instead of creating a NumPy array for each point and then zipping them to extract tuples of \$x\$-coordinates and \$y\$-coordinates, create one array for all the points:

p = np.random.uniform(0., 1000., (N, 2))


We can extract the \$x\$-coordinates and \$y\$-coordinates, if we need them, by transposing the array:

x, y = p.T


but we won't need to do so until the very end when we pass the data to Matplotlib.

-
The coordinates of the origin can be computed like this:

o = (p.min(axis=0) + p.max(axis=0)) * .5


-
To scale the points about the origin, use:

q = o * (1 - scale) + p * scale


instead of (as in the code in the post):

delta = p - o
q = o + delta * scale


The former is slightly quicker, as it has only two arithmetic operations on arrays as long as p whereas the latter has three.

Putting this together:

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.patches as patches

N = 5
p = np.random.uniform(0., 1000., (N, 2))

# Center of points, defined as the center of the minimal rectangle
# that contains all points.
o = (p.min(axis=0) + p.max(axis=0)) * .5

# Scale factor (0. < scale)
scale = 1.5

# Points scaled about center.
q = o * (1 - scale) + p * scale

ax = plt.subplot(111)
ax.scatter(*p.T, c='b')           # Original points.
ax.scatter(*o, marker='x', c='g') # Center.
ax.scatter(*q.T, c='r')           # Scaled points.
ax.add_patch(patches.Rectangle(p.min(axis=0), *p.ptp(axis=0), fill=False))
plt.show()


Here numpy.ptp stands for peak-to-peak and computes the range of values (maximum − minimum) along an axis in an array.

Code Snippets

p = np.random.uniform(0., 1000., (N, 2))
o = (p.min(axis=0) + p.max(axis=0)) * .5
q = o * (1 - scale) + p * scale
delta = p - o
q = o + delta * scale
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.patches as patches

N = 5
p = np.random.uniform(0., 1000., (N, 2))

# Center of points, defined as the center of the minimal rectangle
# that contains all points.
o = (p.min(axis=0) + p.max(axis=0)) * .5

# Scale factor (0. < scale)
scale = 1.5

# Points scaled about center.
q = o * (1 - scale) + p * scale

ax = plt.subplot(111)
ax.scatter(*p.T, c='b')           # Original points.
ax.scatter(*o, marker='x', c='g') # Center.
ax.scatter(*q.T, c='r')           # Scaled points.
ax.add_patch(patches.Rectangle(p.min(axis=0), *p.ptp(axis=0), fill=False))
plt.show()

Context

StackExchange Code Review Q#159183, answer score: 2

Revisions (0)

No revisions yet.