2

I've been stuck on this annoying problems for eons. I'm trying to write code so that I can scale a line segment meaning if the amount that I was to scale by(for example) is 2 and the current length of the line is 33 it will increase the entire length to 67. Meaning I add half to the beginning and half to the end... new front ---a--------b--- new back... But I'm having trouble translating it into code. Here is an example of the code.. The endpoints method should return the endpoints in a tuple such as (p1, p2)

from point import Point
import math

class Line:
def __init__(self,aPoint=Point(), bPoint=Point()):
    self.firstPoint = aPoint
    self.secondPoint = bPoint

def getEndPoints(self):
    return (self.firstPoint, self.secondPoint)

def scale(self,factor):
    if factor < 1:
       x1 = self.firstPoint.x +(self.secondPoint.x - self.firstPoint.x) * (factor)
       x2 = self.secondPoint.x +(self.firstPoint.x  - self.secondPoint.x) * (factor)
       print(str(x1))
       y1 = self.firstPoint.y +(self.secondPoint.y - self.firstPoint.y) * (factor)
       y2 = self.secondPoint.y +(self.firstPoint.y  - self.secondPoint.y) * (factor)
    else:
       x1 = -(self.firstPoint.x +(self.secondPoint.x - self.firstPoint.x) * (factor))
       x2 = -(self.secondPoint.x +(self.firstPoint.x  - self.secondPoint.x) * (factor))
       y1 = self.firstPoint.y +(self.secondPoint.y - self.firstPoint.y) * (factor)
       y2  = self.secondPoint.y +(self.firstPoint.y  - self.secondPoint.y) * (factor)
    self.firstPoint = Point(x1, y1)
    self.secondPoint = Point(x2, y2)

if __name__ == "__main__":
    p1 = Point(5,5)
    p2 = Point(20,35)
    l1 = Line(p1,p2)
    l1.scale(2)
    p5 = Point(-2.5,-10)
    p6 = Point(27.5,50)
    assert l1.getEndPoints() == (p5,p6)

These tests are not working correctly but the above are.. I'm getting a(5.0, 5.0) and b(20.0, 35.0)


    l1.scale(0.5)

    p5 = Point(8.75,12.5)
    p6 = Point(16.25,27.5)

class Point:
'''Point class represents and manipulates
x,y coordinates.'''

def __init__(self,x=0,y=0):
    '''Create a new point with default
    x,y coordinates at 0,0.'''
    self.x = x
    self.y = y

def distanceTo(self,aPoint):
    return ((self.x-aPoint.x) ** 2 + (self.y-aPoint.y) ** 2)** .5 
Crunch
  • 115
  • 1
  • 4
  • 14
  • 2
    Your method `scale` in the Line class is not included? Please show us what you have done so far – Johan Mar 03 '15 at 06:47
  • I don't think you guys get what I am asking. I wanted to take the line with point a(5,5) to point(20,35) and scale it outwards by 2. This mean I would like to turn the point a into (-2.5, -10) and the point b into (27.5, 50). In terms of length, respectively, I would like to turn 33.54 into 67.08 but I wouldn't like to just extend b, I also want to extend a. – Crunch Mar 03 '15 at 07:16
  • 1
    Well, that’s exactly, what my answer does... – Sebastian Werk Mar 03 '15 at 07:22
  • Could you explain in math terms or either in terms of my code? – Crunch Mar 03 '15 at 07:24

3 Answers3

4

not sure if I get it right but

  1. use linear interpolation (parametric line equation)

    You got line defined by endpoints p0,p1 in form of vectors so any point on it is defined as:

    p(t)=p0+(p1-p0)*t
    

    where p(t) is the point (vector) and t is scalar parameter in range

    t=<0.0,1.0>
    

    if you do not know the vector math then rewrite it to scalars

    x(t)=x0+(x1-x0)*t
    y(t)=y0+(y1-y0)*t
    

    so if t=0 then you get the point p0 and if t=1 then you get the point p1

  2. Now just rescale the t range

    so you have scale s

    t0=0.5-(0.5*s)` ... move from half of line by scale towards p0
    t1=0.5+(0.5*s)` ... move from half of line by scale towards p1
    

    so new endpoints are

    q0=p0+(p1-p0)*t0
    q1=p0+(p1-p0)*t1
    

[edit1] I see it like this

def scale(self,factor):
 t0=0.5*(1.0-factor)
 t1=0.5*(1.0+factor)
 x1 = self.firstPoint.x +(self.secondPoint.x - self.firstPoint.x) * t0
 y1 = self.firstPoint.y +(self.secondPoint.y - self.firstPoint.y) * t0
 x2 = self.firstPoint.x +(self.secondPoint.x - self.firstPoint.x) * t1
 y2 = self.firstPoint.y +(self.secondPoint.y - self.firstPoint.y) * t1
 self.firstPoint = Point(x1, y1)
 self.secondPoint = Point(x2, y2)

Take in mind I do not code in python so handle with prejudice ...

Spektre
  • 49,595
  • 11
  • 110
  • 380
  • I don't think you guys get what I am asking. I wanted to take the line with point a(5,5) to point(20,35) and scale it outwards by 2. This mean I would like to turn the point a into (-2.5, -10) and the point b into (27.5, 50). In terms of length, respectively, I would like to turn 33.54 into 67.08 but I wouldn't like to just extend b, I also want to extend a. – Crunch Mar 03 '15 at 07:17
  • @Crunch if you try to compute the values for your example then you will see you get exactly what you want. `s=2 , t0=-0.5 , t1=+1.5 ... so x0=5+(20-5)*(-0.5)=2.5` .... `t` is the length from start point in [line lengths] – Spektre Mar 03 '15 at 08:00
  • Will this work in the reverse? Because it seems that when number are under 1 point a(5,5) which after scaled by 2 is (-2.5, -10) turns into (8.75, 12.5) when scaled by .5 – Crunch Mar 03 '15 at 08:08
  • @Crunch i am missing negative sign in previous comment (result is -2.5 of coarse :) ) reverse scale should also work if you scale by 2 then the length is double so reverse scale should be `s = 1/2 = 0.5` ... – Spektre Mar 03 '15 at 08:11
  • I just edited into my posting what I interpreted as. Can you help me correct this? – Crunch Mar 03 '15 at 08:33
  • @Crunch see [edit1] in my answer ... you do not need any if statements – Spektre Mar 03 '15 at 08:40
  • When I run it I still get [a(5.0,5.0),b(20.0,35.0)] when I try to scale it from [a(-2.5,-10), b(27.5,50)] by .05 it should be [a(8.75,12.5), b(16.25,27.5)] – Crunch Mar 03 '15 at 08:48
  • The first scale is working correctly "l1.scale(2)" just not the second – Crunch Mar 03 '15 at 08:48
  • `scale=new_size/original_size` so for `[a(-2.5,-10), b(27.5,50)] -> [a(8.75,12.5), b(16.25,27.5)]` you need to `scale=0.25` not `0.5 ` !!! ... by reverse scale I mean scale that will go back to original line ... – Spektre Mar 03 '15 at 08:55
  • I change t0 to (.25*(1.0+factor) and I got the correct a.. But the b is still messed up line/[a(8.75,12.5),b(20.0,35.0)] is the result – Crunch Mar 03 '15 at 09:10
  • @crunch do not change the formula ... change just scale(factor) ... if this does not match your need then draw an image with example what exactly you want to achieve ... – Spektre Mar 03 '15 at 09:45
  • I figured it out. I have one more function that is not functioning and would just like some information on it. It is called closestPoint. How it functions is it tests to see what is the closest point on the line to the point you specify in the the parameters. def getClosestPoint(self,point=Point()): – Crunch Mar 03 '15 at 09:47
3

For a scale factor s, the coordinates of the new points are given by

Xa' = Xa (1+s)/2 + Xb (1-s)/2
Ya' = Ya (1+s)/2 + Yb (1-s)/2


Xb' = Xb (1+s)/2 + Xa (1-s)/2
Yb' = Yb (1+s)/2 + Ya (1-s)/2
1

With the common metrik, you only need to adjust each dimension seperately.

I rewrote some parts of the code, to fit it better to the usual Python style

You might want to work through the things, you are unfamiliar with to save yourself a lot of time in the future.

class Line:
    def __init__(self, point_one, point_two):
        self.point_one = point_one
        self.point_two = point_two

    def __str__(self):
        return 'Line(p1:{},p2:{})'.format(self.point_one, self.point_two)

    @property
    def points(self):
        return self.point_one, self.point_two

    @property
    def length(self):
        return ((self.point_one.x - self.point_two.x)**2 + (self.point_one.y - self.point_two.y)**2)**0.5

    def scale(self, factor):
        self.point_one.x, self.point_two.x = Line.scale_dimension(self.point_one.x, self.point_two.x, factor)
        self.point_one.y, self.point_two.y = Line.scale_dimension(self.point_one.y, self.point_two.y, factor)

    @staticmethod
    def scale_dimension(dim1, dim2, factor):
        base_length = dim2 - dim1
        ret1 = dim1 - (base_length * (factor-1) / 2)
        ret2 = dim2 + (base_length * (factor-1) / 2)
        return ret1, ret2


class Point:
    def __init__(self, x=0, y=0):
        self.x = x
        self.y = y

    def __str__(self):
        return 'Point(x={},y={})'.format(self.x, self.y)

    def __eq__(self, other):
        return self.x == other.x and self.y == other.y


if __name__ == "__main__":
    p1 = Point(5, 5)
    p2 = Point(20, 35)
    l1 = Line(p1, p2)
    print(l1)
    print(l1.length)
    l1.scale(2)
    print(l1)
    print(l1.length)
    p5 = Point(-2.5, -10)
    p6 = Point(27.5, 50)
    assert l1.points == (p5, p6)

Note, that the scale method modifies the orginal line and points. If you want to get a new line, the method should be:

def scale(self, factor):
    x1, x2 = Line.scale_dimension(self.point_one.x, self.point_two.x, factor)
    y1, y2 = Line.scale_dimension(self.point_one.y, self.point_two.y, factor)
    return Line(Point(x1, y1), Point(x2, y2))
Sebastian Werk
  • 1,568
  • 2
  • 17
  • 30
  • I don't think you guys get what I am asking. I wanted to take the line with point a(5,5) to point(20,35) and scale it outwards by 2. This mean I would like to turn the point a into (-2.5, -10) and the point b into (27.5, 50). In terms of length, respectively, I would like to turn 33.54 into 67.08 but I wouldn't like to just extend b, I also want to extend a. – Crunch Mar 03 '15 at 07:18
  • Did you try this code? The assert passes, the points are those, you want to have and the length fits. – Sebastian Werk Mar 03 '15 at 07:25
  • Okay thanks this works but when I try it on print(str(l1)) #p5 = Point(8.75,12.5) #p6 = Point(16.25,27.5) #assert l1.getEndPoints() == (p5,p6) or l1.getEndPoints() == (p6,p5) – Crunch Mar 03 '15 at 07:51