# -*- coding: utf-8 -*-
"""interpolacao_linear.ipynb

Automatically generated by Colab.

Original file is located at
    https://colab.research.google.com/drive/1SK3m5gHieartoYFtbLYprPY-6p6Z2IXg
"""

# ============================================================
# Aula prática: Interpolação Linear entre Pontos
# Cálculo Numérico
# ============================================================

import math
import pandas as pd
import matplotlib.pyplot as plt


# ------------------------------------------------------------
# 1. Pontos originais
# ------------------------------------------------------------

pontos = [
    (1.56, 1.56),  # P1
    (3.75, 0.58),  # P2
    (5.99, 8.66),  # P3
    (7.32, 6.01),  # P4
    (9.51, 7.08)   # P5
]

nomes_pontos = ["P1", "P2", "P3", "P4", "P5"]

limite = 2  # distância máxima desejada entre pontos consecutivos


# ------------------------------------------------------------
# 2. Função para calcular a distância euclidiana
# ------------------------------------------------------------

def distancia(p1, p2):
    """
    Calcula a distância euclidiana entre dois pontos no plano.

    p1 = (x1, y1)
    p2 = (x2, y2)
    """
    x1, y1 = p1
    x2, y2 = p2

    dx = x2 - x1
    dy = y2 - y1

    return math.sqrt(dx**2 + dy**2)


# ------------------------------------------------------------
# 3. Função de interpolação linear paramétrica
# ------------------------------------------------------------

def interpolar(p1, p2, t):
    """
    Gera um ponto intermediário entre p1 e p2 usando interpolação linear.

    t = 0 retorna p1
    t = 1 retorna p2
    0 < t < 1 retorna um ponto intermediário
    """
    x1, y1 = p1
    x2, y2 = p2

    x = (1 - t) * x1 + t * x2
    y = (1 - t) * y1 + t * y2

    return (x, y)


# ------------------------------------------------------------
# 4. Função para gerar pontos intermediários entre dois pontos
# ------------------------------------------------------------

def pontos_intermediarios(p1, p2, limite=2):
    """
    Gera pontos intermediários entre p1 e p2 para que
    cada segmento tenha comprimento máximo igual ao limite.

    Retorna:
    - lista de pontos intermediários
    - distância original entre p1 e p2
    - número de segmentos gerados
    """
    d = distancia(p1, p2)

    # n é a quantidade de segmentos necessários
    n = math.ceil(d / limite)

    novos = []

    # Se n = 1, não há ponto intermediário.
    # Se n = 2, cria 1 ponto.
    # Se n = 5, cria 4 pontos.
    for k in range(1, n):
        t = k / n
        ponto = interpolar(p1, p2, t)
        novos.append((ponto, t))

    return novos, d, n


# ------------------------------------------------------------
# 5. Mostrar os pontos originais
# ------------------------------------------------------------

print("PONTOS ORIGINAIS")
print("-" * 50)

df_originais = pd.DataFrame({
    "Ponto": nomes_pontos,
    "x": [p[0] for p in pontos],
    "y": [p[1] for p in pontos]
})

display(df_originais)


# ------------------------------------------------------------
# 6. Calcular distâncias e gerar pontos interpolados
# ------------------------------------------------------------

resultado = []
registros_distancias = []
registros_interpolados = []

contador_interpolado = 1

for i in range(len(pontos) - 1):
    p_atual = pontos[i]
    p_proximo = pontos[i + 1]

    nome_atual = nomes_pontos[i]
    nome_proximo = nomes_pontos[i + 1]

    # Adiciona o ponto original atual ao resultado final
    resultado.append({
        "nome": nome_atual,
        "tipo": "Original",
        "x": p_atual[0],
        "y": p_atual[1]
    })

    # Calcula os pontos intermediários
    novos, d, n = pontos_intermediarios(p_atual, p_proximo, limite)

    registros_distancias.append({
        "Trecho": f"{nome_atual} -> {nome_proximo}",
        "Distância original": d,
        "Limite": limite,
        "n segmentos": n,
        "Pontos internos criados": n - 1
    })

    # Adiciona os pontos interpolados ao resultado final
    for ponto, t in novos:
        nome_interpolado = f"I{contador_interpolado}"

        resultado.append({
            "nome": nome_interpolado,
            "tipo": "Interpolado",
            "x": ponto[0],
            "y": ponto[1]
        })

        registros_interpolados.append({
            "Ponto": nome_interpolado,
            "Entre": f"{nome_atual} -> {nome_proximo}",
            "t": t,
            "x": ponto[0],
            "y": ponto[1]
        })

        contador_interpolado += 1

# Adiciona o último ponto original
resultado.append({
    "nome": nomes_pontos[-1],
    "tipo": "Original",
    "x": pontos[-1][0],
    "y": pontos[-1][1]
})


# ------------------------------------------------------------
# 7. Exibir tabela de distâncias
# ------------------------------------------------------------

print("\nDISTÂNCIAS ENTRE PONTOS CONSECUTIVOS")
print("-" * 50)

df_distancias = pd.DataFrame(registros_distancias)

df_distancias["Distância original"] = df_distancias["Distância original"].round(3)

display(df_distancias)


# ------------------------------------------------------------
# 8. Exibir tabela dos pontos interpolados
# ------------------------------------------------------------

print("\nPONTOS INTERPOLADOS")
print("-" * 50)

df_interpolados = pd.DataFrame(registros_interpolados)

df_interpolados["t"] = df_interpolados["t"].round(3)
df_interpolados["x"] = df_interpolados["x"].round(3)
df_interpolados["y"] = df_interpolados["y"].round(3)

display(df_interpolados)


# ------------------------------------------------------------
# 9. Exibir tabela final com pontos originais + interpolados
# ------------------------------------------------------------

print("\nPONTOS FINAIS: ORIGINAIS + INTERPOLADOS")
print("-" * 50)

df_resultado = pd.DataFrame(resultado)

df_resultado["x"] = df_resultado["x"].round(3)
df_resultado["y"] = df_resultado["y"].round(3)

display(df_resultado)


# ------------------------------------------------------------
# 10. Verificar distância entre os pontos finais consecutivos
# ------------------------------------------------------------

print("\nDISTÂNCIA ENTRE OS PONTOS FINAIS CONSECUTIVOS")
print("-" * 50)

verificacao = []

for i in range(len(resultado) - 1):
    p1 = (resultado[i]["x"], resultado[i]["y"])
    p2 = (resultado[i + 1]["x"], resultado[i + 1]["y"])

    d = distancia(p1, p2)

    verificacao.append({
        "Trecho": f'{resultado[i]["nome"]} -> {resultado[i + 1]["nome"]}',
        "Distância": d,
        "Respeita limite?": "Sim" if d <= limite else "Não"
    })

df_verificacao = pd.DataFrame(verificacao)
df_verificacao["Distância"] = df_verificacao["Distância"].round(3)

display(df_verificacao)


# ------------------------------------------------------------
# 11. Separar coordenadas para os gráficos
# ------------------------------------------------------------

x_original = [p[0] for p in pontos]
y_original = [p[1] for p in pontos]

x_interp = [p["x"] for p in resultado]
y_interp = [p["y"] for p in resultado]


# ------------------------------------------------------------
# 12. Gráfico 1: pontos originais
# ------------------------------------------------------------

plt.figure(figsize=(8, 5))

plt.plot(x_original, y_original, 'o', markersize=8, label='Pontos originais')

for nome, x, y in zip(nomes_pontos, x_original, y_original):
    plt.text(x + 0.08, y + 0.08, nome)

plt.title("Pontos originais")
plt.xlabel("x")
plt.ylabel("y")
plt.grid(True)
plt.legend()
plt.show()


# ------------------------------------------------------------
# 13. Gráfico 2: interpolação linear
# ------------------------------------------------------------

plt.figure(figsize=(8, 5))

plt.plot(x_original, y_original, 'o', markersize=8, label='Pontos originais')
plt.plot(x_interp, y_interp, '-o', markersize=5, label='Pontos após interpolação')

for p in resultado:
    plt.text(p["x"] + 0.08, p["y"] + 0.08, p["nome"], fontsize=9)

plt.title("Interpolação linear entre pontos consecutivos")
plt.xlabel("x")
plt.ylabel("y")
plt.grid(True)
plt.legend()
plt.show()


# ------------------------------------------------------------
# 14. Gráfico 3: comparação antes e depois
# ------------------------------------------------------------

plt.figure(figsize=(9, 5))

plt.plot(x_original, y_original, 'o--', markersize=8, label='Antes: pontos originais')
plt.plot(x_interp, y_interp, '-o', markersize=5, label='Depois: pontos interpolados')

plt.title("Comparação: antes e depois da interpolação")
plt.xlabel("x")
plt.ylabel("y")
plt.grid(True)
plt.legend()
plt.show()


# ------------------------------------------------------------
# 15. Teste com outros limites
# ------------------------------------------------------------

def gerar_resultado_com_limite(pontos, nomes_pontos, limite):
    """
    Gera a lista final de pontos para um determinado limite de distância.
    """
    resultado_temp = []
    contador = 1

    for i in range(len(pontos) - 1):
        p_atual = pontos[i]
        p_proximo = pontos[i + 1]

        resultado_temp.append({
            "nome": nomes_pontos[i],
            "tipo": "Original",
            "x": p_atual[0],
            "y": p_atual[1]
        })

        novos, d, n = pontos_intermediarios(p_atual, p_proximo, limite)

        for ponto, t in novos:
            resultado_temp.append({
                "nome": f"I{contador}",
                "tipo": "Interpolado",
                "x": ponto[0],
                "y": ponto[1]
            })
            contador += 1

    resultado_temp.append({
        "nome": nomes_pontos[-1],
        "tipo": "Original",
        "x": pontos[-1][0],
        "y": pontos[-1][1]
    })

    return resultado_temp


# Testar diferentes valores de limite
limites = [1, 2, 3]

for lim in limites:
    res = gerar_resultado_com_limite(pontos, nomes_pontos, lim)

    x_res = [p["x"] for p in res]
    y_res = [p["y"] for p in res]

    plt.figure(figsize=(8, 5))
    plt.plot(x_original, y_original, 'o--', markersize=8, label='Originais')
    plt.plot(x_res, y_res, '-o', markersize=4, label=f'Interpolados - limite={lim}')

    plt.title(f"Interpolação com distância máxima = {lim}")
    plt.xlabel("x")
    plt.ylabel("y")
    plt.grid(True)
    plt.legend()
    plt.show()

    print(f"Limite = {lim}")
    print(f"Quantidade total de pontos: {len(res)}")
    print(f"Pontos novos criados: {len(res) - len(pontos)}")
    print("-" * 50)